Местоположение локальной переменной из информации DWARF в ARM

У меня есть программа C в файле delay.c:

void delay(int num)
{
   volatile int i;
   for(i=0; i<num; i++);
}

Затем я компилирую программу с помощью gcc 4.6.3 на эмуляторе ARM (точнее, armel) с помощью команды gcc -g -O1 -o delay.o delay.c. Сборка в delay.o такая:

00000000 <delay>:
   0:   e24dd008    sub sp, sp, #8
   4:   e3a03000    mov r3, #0
   8:   e58d3004    str r3, [sp, #4]
   c:   e59d3004    ldr r3, [sp, #4]
  10:   e1500003    cmp r0, r3
  14:   da000005    ble 30 <delay+0x30>
  18:   e59d3004    ldr r3, [sp, #4]
  1c:   e2833001    add r3, r3, #1
  20:   e58d3004    str r3, [sp, #4]
  24:   e59d3004    ldr r3, [sp, #4]
  28:   e1530000    cmp r3, r0
  2c:   bafffff9    blt 18 <delay+0x18>
  30:   e28dd008    add sp, sp, #8
  34:   e12fff1e    bx  lr

Я хочу выяснить, где находится переменная i в стеке функции delay из отладочной информации. Ниже представлена ​​информация о delay и i в разделе .debug_info:

<1><25>: Abbrev Number: 2 (DW_TAG_subprogram)
   <26>   DW_AT_external    : 1
   <27>   DW_AT_name        : (indirect string, offset: 0x19): delay
   <2b>   DW_AT_decl_file   : 1
   <2c>   DW_AT_decl_line   : 1
   <2d>   DW_AT_prototyped  : 1
   <2e>   DW_AT_low_pc      : 0x0
   <32>   DW_AT_high_pc     : 0x38
   <36>   DW_AT_frame_base  : 0x0      (location list)
   <3a>   DW_AT_sibling     : <0x59>
...
<2><4b>: Abbrev Number: 4 (DW_TAG_variable)
   <4c>   DW_AT_name        : i
   <4e>   DW_AT_decl_file   : 1
   <4f>   DW_AT_decl_line   : 3
   <50>   DW_AT_type        : <0x60>
   <54>   DW_AT_location    : 0x20     (location list)

Он показывает, что местоположение i находится в списке местоположений. Итак, я вывожу список местоположений:

Offset   Begin    End      Expression
00000000 00000000 00000004 (DW_OP_breg13 (r13): 0)
00000000 00000004 00000038 (DW_OP_breg13 (r13): 8)
00000000 <End of list>
00000020 0000000c 00000020 (DW_OP_fbreg: -12)
00000020 00000024 00000028 (DW_OP_reg3 (r3))
00000020 00000028 00000038 (DW_OP_fbreg: -12)
00000020 <End of list>

Для адресов с 4 по 38 основание кадра delay должно быть r13 + 8. Таким образом, от адреса c до 20 и от адреса 28 до 38 местоположение i равно r13 + 8 -12 = r13 - 4.

Однако из сборки мы можем узнать, что места r13 - 4 нет, а i, по-видимому, находится в месте r13 + 4.

Я пропустил какой-то шаг расчета? Кто-нибудь может объяснить разницу в местоположении i между расчетом из отладочной информации и в сборке?

Заранее спасибо!


person LostBenjamin    schedule 17.11.2017    source источник


Ответы (2)


TL;DR Анализ в вопросе верен, и несоответствие является ошибкой в ​​одном из компонентов gcc (GNU Arm Embedded Toolchain — очевидное место для регистрации).

В его нынешнем виде этот другой ответ неверен, поскольку он ошибочно объединяет значение указателя стека при оценке выражения местоположения с более раннее значение указателя стека при входе в функцию.

Что касается DWARF, положение i зависит от счетчика программ. Рассмотрим, например, текстовый адрес delay+0x18. В этот момент местоположение i определяется как DW_OP_fbreg(-12), т. е. на 12 байт ниже базы кадра. База кадра задается атрибутом DW_AT_frame_base родителя DW_TAG_subprogram, который в данном случае также зависит от счетчика программ: для 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 и запустите до второго вхождения (где мы ожидаем, что 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 гном неправильно кодирует местоположение 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) 
person Robert Harris    schedule 30.06.2018

Я не согласен с asm-анализом ОП:

00000000 <delay>:                         ; so far, let's suppose sp = sp(0) 
0:   e24dd008    sub sp, sp, #8           ; sp = sp(0) - 8
4:   e3a03000    mov r3, #0               ; r3 = 0 
8:   e58d3004    str r3, [sp, #4]         ; store the value of r3 in (sp + 4)
c:   e59d3004    ldr r3, [sp, #4]         ; load (sp + 4) in r3
10:   e1500003    cmp r0, r3              ; compare r3 and r0  
14:   da000005    ble 30 <delay+0x30>     ; go to end of loop
18:   e59d3004    ldr r3, [sp, #4]        ; i is in r3, and it is being loaded from
                                          ; (sp + 4), that is, 
                                          ; sp(i) = sp(0) - 8 + 4 = sp(0) - 4
1c:   e2833001    add r3, r3, #1          ; r3 = r3 + 1, that is, increment i
20:   e58d3004    str r3, [sp, #4]        ; store i (which is in r3) in (sp + 4),
                                          ; being again sp(i) = sp(0) - 8 + 4 = \
                                          ; sp(0) - 4
24:   e59d3004    ldr r3, [sp, #4]        ; load sp + 4 in r3
28:   e1530000    cmp r3, r0              ; compare r3 and r0
2c:   bafffff9    blt 18 <delay+0x18>     ; go to init of loop
30:   e28dd008    add sp, sp, #8          ; sp = sp + 8
34:   e12fff1e    bx  lr                  ; 

Таким образом, i находится в sp(0) - 4, что соответствует анализу карликов (который говорит, что i находится в 0 + 8 - 12).

Изменить, чтобы добавить информацию о моем анализе DWARF:

Согласно этой строке: 00000020 0000000c 00000020 (DW_OP_fbreg: -12), будучи DW_OP_fbreg:

Операция DW_OP_fbreg предоставляет смещение LEB128 со знаком от адреса, указанного в описании местоположения в атрибуте DW_AT_frame_base текущей функции. (Обычно это регистр «указателя стека» плюс-минус некоторое смещение. В более сложных системах это может быть список местоположений, который корректирует смещение в соответствии с изменениями в указателе стека при изменении ПК.)

, адрес frame_base + offset, где:

  • frame_base : указатель стека +/- некоторое смещение, и, согласно предыдущей строке (00000000 00000004 00000038 (DW_OP_breg13 (r13): 8)), от 00000004 до 00000038 имеет смещение +8 (r13 — это SP)
  • смещение: очевидно, это -12

Учитывая это, DWARF указывает, что он указывает на sp(0) + 8 - 12 = sp(0) - 4

person Jose    schedule 04.07.2018
comment
Согласно анализу OP, DWARF говорит, что i находится в sp(0) - 12, но asm показывает, что он находится в sp(0) - 4. - person Robert Harris; 04.07.2018
comment
@RobertHarris Насколько я понял, анализ OP говорит, что согласно asm, i находится в SP со смещением +4 ([... очевидно находится в месте r13 + 4...]), однако карлик сообщает i как SP со смещением -4 ([... расположение i равно r13 + 8 -12 = r13 - 4 ...]). ИМО, карликовый анализ имеет смысл (0 + 8 - 12, смещение -4), но не ассемблерный анализ. Скорее всего, я ошибаюсь, но я не вижу, где ОП говорит, что двард сообщает SP - 12. - person Jose; 05.07.2018
comment
DWARF выражает местоположение i как смещение от базы кадра. Например. при задержке+0x18 оно на 12 байт ниже текущего значения базы кадра. Однако для этого ПК база кадра на 8 байт выше текущего значения r13; фактически, для случая OP база кадра представляет собой фиксированное значение, равное указателю стека при входе в функцию. Следовательно, при задержке +0x18 DWARF определяет местоположение i как 12 байтов ниже исходного значения r13, т. е. на 4 байта ниже текущего значения r13. Сборка показывает, что i на 4 байта выше текущего значения r13. - person Robert Harris; 05.07.2018
comment
Ваш собственный вывод неверен: например, при задержке + 0x18 расположение i равно (значение r13 при задержке + 0x18) + 8 - 12 = sp (0) - 12 = sp - 4. - person Robert Harris; 05.07.2018