我在代码中遇到了一个错误,其中本地堆栈变量的地址 是无效的。我一直在尝试使用 lldb 来调试它。
在下面的lldb提示符中,您可以看到
&dominance_frontier
给出了地址
0x0000000000000001
随后会在 hash_table_init
呼叫线路 121 上引发 SIGSEGV。
然而&dominator_tree_adj
,给出了一个有效的地址。我完全困惑
为什么会出现这种情况。
Process 70998 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step over
frame #0: 0x0000000100006fd0 ir`ComputeDominanceFrontier(function=0x00006000030f8000) at dominators.c:121:9
111 struct Array postorder_traversal = postorder (function->entry_basic_block);
112 struct DFAConfiguration config = DominatorDFAConfiguration (function);
113 struct DFAResult result = run_DFA (&config, function);
114
115 HashTable dominator_tree_adj = ComputeDominatorTree (function, &result);
116 printf("%p\n", &dominator_tree_adj);
117 HashTable dominance_frontier;
118 printf("%ld\n", sizeof(dominance_frontier));
119
120
-> 121 hash_table_init (&dominance_frontier);
122 // Compute the transpose graph from the dominator tree adjacency list
123 // Each node is guaranteed to have only one direct predecessor, since
124 // each node can only have one immediate dominator. We will need this
125 // in the DF algorithm below
126 HashTable dominator_tree_transpose;
127 hash_table_init (&dominator_tree_transpose);
128
129 struct HashTableEntry *entry;
130 size_t entry_iter = 0;
131
Target 0: (ir) stopped.
(lldb) p &dominance_frontier
(HashTable *) 0x0000000000000001
(lldb) p &dominator_tree_adj
(HashTable *) 0x000000016fdfef38
我一直在用
make DEBUG=yes
编译我的代码
OPT = -O3
FLAGS = -Wall -Wextra
CC = cc
OBJECTS = ir_parser.o \
main.o \
threeaddr_parser.o \
instruction.o \
function.o \
basicblock.o \
constant.o \
utils.o \
value.o \
array.o \
mem.o \
map.o \
dfa.o \
dominators.o
ifdef DEBUG
OPT = -g
else
OPT = -O3
endif
all: $(OBJECTS)
$(CC) $(OPT) $(FLAGS) $^ -o ir
%.o: %.c *.h
$(CC) $(OPT) $(FLAGS) -c $< -o $@
clean:
rm *.o
这是段错误的位置:
Process 10619 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x9)
frame #0: 0x0000000100005060 ir`_hash_table_init(table=0x0000000000000001, size=10) at map.c:21:21
11 // Open addressing, linear probe hash table.
12
13 unsigned long uint64_t_hash_function (uint64_t key)
14 {
15 // Simple hash function for demonstration
16 return key;
17 }
18
19 void _hash_table_init (HashTable *table, size_t size)
20 {
-> 21 table->size = size;
22 table->count = 0;
23 table->buckets = ir_calloc (table->size, sizeof (HashTableEntry));
24 }
25 void hash_table_init (HashTable *table)
26 {
27 _hash_table_init (table, MAP_INIT_SIZE_CNT);
28 }
29
30 // Create a new hash table
31 HashTable *hash_table_create (size_t size)
Target 0: (ir) stopped.
输入文件:
fn test4(%1, %2) {
alloca %9, 5
add %3, %1, %2
store %9, %3
cmp %4, %1, %2
jumpif 1, %4
sub %5, %3, 1
jumpif 2, %5
1:
add %6, %1, 20
jump 3
2:
add %7, %2, 30
3:
sub %8, %3, %3
}
./ir -f path/to/input/file
我正在使用 Apple clang 版本 15.0.0 (clang-1500.3.9.4)
HashTable ComputeDominanceFrontier (struct Function *function)
^^^^^^^^^
void ComputeDominanceFrontier (struct Function *function);
^^^^
哎呀。
函数
ComputeDominanceFrontier
是从main.c
调用的,其中包含dominators.h
,因此调用者认为它返回void
。 但根据dominators.c
中的定义,它实际上返回的是HashTable
。 这种不匹配是导致崩溃的原因,如下所述。
编译器没有捕获到这一点,因为您没有将
dominators.h
包含到 dominators.c
中,因此在编译 dominators.c
时,它不知道其他一些源文件看到了冲突的声明。 因此,当标头声明全局函数时,应始终确保标头包含在定义该函数的源文件中。 您可以使用 -Wmissing-prototypes
来帮助强制执行此操作,只要在未事先声明的情况下定义全局函数,它就会发出警告。 请参阅针对范围内没有原型定义的函数的编译器警告?
发生的情况是这样的:在arm64上,像许多其他平台一样,当函数返回无法在寄存器中返回的
struct
类型时,它会通过“隐藏引用”返回:调用者为返回值分配空间,并传递一个额外的隐藏参数和该空间的地址。 在arm64上,额外的参数在寄存器x8
中传递。 然后被调用的函数负责将其返回值复制到该空间中。
在这种情况下,由于
main
不知道 ComputeDominanceFrontier
正在返回 struct
类型,因此它没有分配这样的空间,并留下 x8
包含垃圾(在你的情况下,它的值恰好是1
)。
如果天真地编译,当
dominance_frontier
从堆栈复制到伪造的返回值地址时,您会期望在函数末尾看到崩溃。 听起来这就是您在 Ubuntu 测试中看到的情况(让我猜一下,使用 gcc 而不是 clang?)。
但是,编译器可以对此进行优化:不使用
dominance_frontier
的堆栈空间,而是使用调用者分配的返回值空间。 然后我们不需要在返回之前复制该数据,因为它是“就地”填充的。 看来,即使优化“关闭”,clang 也会执行此优化。 因此,dominance_frontier
并不真正存在于堆栈中,并且&dominance_frontier
不是堆栈地址,而是调用者传递的返回值地址 - 在这种情况下是垃圾。