Ответ 1
TL; DR. Анализ в вопросе правильный, и несоответствие является ошибкой в одном из компонентов gcc (GNU Arm Embedded Toolchain является очевидным местом для регистрации одного).
Как бы то ни было, этот другой ответ неверен, поскольку он ошибочно связывает значение указателя стека при оценке выражения местоположения с более ранним значением указателя стека при входе в функцию.
Что касается DWARF, расположение i
зависит от счетчика программ. Рассмотрим, например, delay+0x18
текстового адреса delay+0x18
. На этом этапе местоположение i
задается DW_OP_fbreg(-12)
, то есть 12 байт ниже базы кадра. База кадра задается родительским атрибутом DW_TAG_subprogram
DW_AT_frame_base
который в этом случае также зависит от счетчика программы: для delay+0x18
его выражение равно DW_OP_breg13(8)
, то есть r13 + 8
. Важно отметить, что этот расчет использует текущее значение r13
, то есть значение r13
когда счетчик программ равен delay+0x18
.
Таким образом, DWARF утверждает, что при delay+0x18
i
находится на r13 + 8 - 12
, т.е. на 4 байта ниже нижней части существующего стека. Проверка сборки показывает, что при delay+018
i
должно быть найдено 4 байта над нижней частью стека. Поэтому DWARF ошибочен, и все, что сгенерировано, является дефектным.
Можно продемонстрировать ошибку с помощью gdb
с простой оболочкой вокруг тестового примера, заданного в вопросе:
$ cat delay.c
void delay(int num)
{
volatile int i;
for(i=0; i<num; i++);
}
$ gcc-4.6 -g -O1 -c delay.c
$ cat main.c
void delay(int);
int main(int argc, char **argv) {
delay(3);
}
$ gcc-4.6 -o test main.c delay.o
$ gdb ./test
.
.
.
(gdb)
Установите точку останова с delay+0x18
и delay+0x18
ко второму вступлению (где мы ожидаем, что i
будет 1):
(gdb) break *delay+0x18
Breakpoint 1 at 0x103cc: file delay.c, line 4.
(gdb) run
Starting program: /home/pi/test
Breakpoint 1, 0x000103cc in delay (num=3) at delay.c:4
4 for(i=0; i<num; i++);
(gdb) cont
Continuing.
Breakpoint 1, 0x000103cc in delay (num=3) at delay.c:4
4 for(i=0; i<num; i++);
(gdb)
Мы знаем из разборки, что i
на четыре байта выше указателя стека. Действительно, вот оно:
(gdb) print *((int *)($r13 + 4))
$1 = 1
(gdb)
Однако поддельный DWARF означает, что gdb выглядит не в том месте:
(gdb) print i
$2 = 0
(gdb)
Как объяснялось выше, DWARF неправильно указывает местоположение i
в четырех байтах ниже указателя стека. Там есть нуль, следовательно, сообщаемое значение i
:
(gdb) print *((int *)($r13 - 4))
$3 = 0
(gdb)
Это не совпадение. Магическое число, записанное в это фиктивное местоположение под указателем стека, появляется, когда gdb
предлагается распечатать i
:
(gdb) set *((int *)($r13 - 4)) = 42
(gdb) print i
$6 = 42
(gdb)
Таким образом, при delay+0x18
DWARF неправильно кодирует местоположение i
как r13 - 4
хотя его истинное местоположение - r13 + 4
.
Можно сделать еще один шаг, отредактировав блок компиляции вручную и заменив DW_OP_fbreg(-12)
(байты 0x91 0x74
) на DW_OP_fbreg(-4)
(байты 0x91 0x7c
). Это дает
$ readelf --debug-dump=loc delay.modified.o
Contents of the .debug_loc section:
Offset Begin End Expression
00000000 00000000 00000004 (DW_OP_breg13 (r13): 0)
0000000c 00000004 00000038 (DW_OP_breg13 (r13): 8)
00000018 <End of list>
00000020 0000000c 00000020 (DW_OP_fbreg: -4)
0000002c 00000024 00000028 (DW_OP_reg3 (r3))
00000037 00000028 00000038 (DW_OP_fbreg: -4)
00000043 <End of list>
$
Другими словами, DWARF был скорректирован так, что, например, при delay+0x18
местоположение i
задается как frame base - 4 = r13 + 8 - 4 = r13 + 4
, соответствующее сборке. Повторение эксперимента gdb с исправленным DWARF показывает ожидаемое значение i
каждый раз вокруг цикла:
$ gcc-4.6 -o test.modified main.c delay.modified.o
$ gdb ./test.modified
.
.
.
(gdb) break *delay+0x18
Breakpoint 1 at 0x103cc: file delay.c, line 4.
(gdb) run
Starting program: /home/pi/test.modified
Breakpoint 1, 0x000103cc in delay (num=3) at delay.c:4
4 for(i=0; i<num; i++);
(gdb) print i
$1 = 0
(gdb) cont
Continuing.
Breakpoint 1, 0x000103cc in delay (num=3) at delay.c:4
4 for(i=0; i<num; i++);
(gdb) print i
$2 = 1
(gdb) cont
Continuing.
Breakpoint 1, 0x000103cc in delay (num=3) at delay.c:4
4 for(i=0; i<num; i++);
(gdb) print i
$3 = 2
(gdb) cont
Continuing.
[Inferior 1 (process 30954) exited with code 03]
(gdb)