Индекс массива выходит за границы, но GDB сообщает неверную строку - почему? - PullRequest
0 голосов
/ 11 января 2019

Я новичок в C ++. Я обнаружил странное явление. GDB не может дать номер строки основной причины ошибки в этом коде.

#include <array>

using std::array;

int main(int argc, char **argv) {

    array<double, 3> edgePoint1{0, 0, 0};
    array<double, 3> edgePoint2{0, 0, 0};
    array<double, 3> edgePoint3{0, 0, 0};
    array<array<double, 3>, 3> edgePoints{};
    edgePoints[0] = edgePoint1;
    edgePoints[1] = edgePoint2;
    edgePoints[3] = edgePoint3;
    return 0;
}

Строка 13 является корнем проблемы. Но когда я использую 'bt' в GBD, он печатает строку 15. Почему?

Program received signal SIGABRT, Aborted.
0x00007f51f3133d7f in raise () from /usr/lib/libc.so.6
(gdb) bt
#0  0x00007f51f3133d7f in raise () from /usr/lib/libc.so.6
#1  0x00007f51f311e672 in abort () from /usr/lib/libc.so.6
#2  0x00007f51f3176878 in __libc_message () from /usr/lib/libc.so.6
#3  0x00007f51f3209415 in __fortify_fail_abort () from /usr/lib/libc.so.6
#4  0x00007f51f32093c6 in __stack_chk_fail () from /usr/lib/libc.so.6
#5  0x0000556e72f282b1 in main (argc=1, argv=0x7ffdc9299218) at /home/wzx/CLionProjects/work/test1.cpp:15
#6  0x0000000000000000 in ?? ()

Ответы [ 3 ]

0 голосов
/ 11 января 2019

Отладчик диагностирует практических ошибок. Вещи, которые происходят в результате ошибок / ошибок в вашем коде, после очень сложного процесса перевода исходного кода в реальную программу, которую может запустить компьютер. Он не анализирует исходный код C ++ на наличие ошибок / ошибок в вашем коде, и при этом он теоретически не способен на это (по крайней мере, в общем случае). Здесь практическая ошибка в том, что переполнение буфера повредило «стек». Вы видите только тот симптом, о котором сообщалось, а не его первоначальную причину (сам переполнение буфера).

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

Epic car crash analogy (by me)

(Обратите внимание, что телефон пролетел через разбитое окно и приземлился на землю рядом с деревом: его нигде не было рядом с рукой водителя - хотя причиной аварии было то, что он был в руке водителя. A Хороший полицейский поймет, что телефон, вероятно, когда-то был внутри автомобиля, и, основываясь на наполовину написанном текстовом сообщении, отображаемом на его экране, он, вероятно, находился в руке водителя во время аварии. Ваша честь. Решение: прекратить текстовые сообщения во время вождения.)

Это факт жизни с C ++, поэтому мы должны уделять пристальное внимание нашему коду при его написании, чтобы мы не «стреляли себе в ногу». Здесь вам очень повезло, что вы потерпели крах, в противном случае, возможно, вы полностью пропустили ошибку и вместо этого просто увидели неожиданное / странное поведение!

Со временем, по мере того, как вы набираетесь опыта, вы становитесь более привыкшим к этому и приобретаете навыки, чтобы смотреть «вокруг» или «рядом» с сообщаемой строкой, чтобы увидеть, какая логическая ошибка привела к практической проблеме. По большей части это умственное сопоставление с образцом. Это также одна из причин, почему вы не можете «выучить C ++ за 21 день»!

Некоторые инструменты do существуют, чтобы упростить эту задачу. Инструменты статического анализа могут смотреть на ваш код и иногда обнаруживать, когда вы используете невозможный индекс массива. Контейнеры (например, array и vector) могут быть реализованы с дополнительной проверкой границ (для at() это требуется; для op[] некоторые реализации добавляют его для удобства в режиме отладки). Объедините инструменты с опытом для большого успеха!

0 голосов
/ 11 января 2019

Хотя верно то, что сказано Lightness Races in Orbit, также верно и то, что когда вы компилируете с отладочной информацией (то есть, используя gcc / clang параметр -g), компилятор выдает line information, позволяя теоретически отладчику связать каждая машинная инструкция с номером строки источника, даже при компиляции с -O3, где действительно fancy оптимизации происходят.

Сказал, что объяснение того, почему gdb говорит вам, что программа потерпела крах в строке 15, очень просто: крах действительно не произошел в строке 13. Достаточно взглянуть на трассировку стека [я скомпилировал вашу программу с gdb в Linux]:

(gdb) bt
#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
#1  0x00007ffff7a24801 in __GI_abort () at abort.c:79
#2  0x00007ffff7a6d897 in __libc_message (action=action@entry=do_abort,
    fmt=fmt@entry=0x7ffff7b9a988 "*** %s ***: %s terminated\n") at ../sysdeps/posix/libc_fatal.c:181
#3  0x00007ffff7b18cd1 in __GI___fortify_fail_abort (need_backtrace=need_backtrace@entry=false,
    msg=msg@entry=0x7ffff7b9a966 "stack smashing detected") at fortify_fail.c:33
#4  0x00007ffff7b18c92 in __stack_chk_fail () at stack_chk_fail.c:29
#5  0x00005555555547e2 in main (argc=1, argv=0x7fffffffdde8) at crash.cpp:15

Как вы можете видеть в кадре # 4, ваша программа не вылетала из-за переполнения буфера, а из-за защиты стека компилятора (функция __stack_chk_fail)

Поскольку это не код, написанный вами, а автоматически генерируемый компилятором именно для обнаружения таких ошибок, строчная информация не может быть реальной. Компилятор просто использовал строку 15, потому что именно там заканчивается ваша функция main() и, конечно, место, где, если вы посмотрите на код разборки, компилятор испустил код, используя stack sentinels для обнаружения stack corruption.

Чтобы увидеть всю картинку еще лучше, вот код разборки (просто используйте disass /s main в gdb, чтобы увидеть ее):

13      edgePoints[3] = edgePoint3;
   0x000000000000079e <+308>:   lea    rax,[rbp-0x50]
   0x00000000000007a2 <+312>:   mov    esi,0x3
   0x00000000000007a7 <+317>:   mov    rdi,rax
   0x00000000000007aa <+320>:   call   0x7e4 <std::array<std::array<double, 3ul>, 3ul>::operator[](unsigned long)>
   0x00000000000007af <+325>:   mov    rcx,rax
   0x00000000000007b2 <+328>:   mov    rax,QWORD PTR [rbp-0x70]
   0x00000000000007b6 <+332>:   mov    rdx,QWORD PTR [rbp-0x68]
   0x00000000000007ba <+336>:   mov    QWORD PTR [rcx],rax
   0x00000000000007bd <+339>:   mov    QWORD PTR [rcx+0x8],rdx
   0x00000000000007c1 <+343>:   mov    rax,QWORD PTR [rbp-0x60]
   0x00000000000007c5 <+347>:   mov    QWORD PTR [rcx+0x10],rax

14      return 0;
   0x00000000000007c9 <+351>:   mov    eax,0x0

15  }
   0x00000000000007ce <+356>:   mov    rdx,QWORD PTR [rbp-0x8]
   0x00000000000007d2 <+360>:   xor    rdx,QWORD PTR fs:0x28
   0x00000000000007db <+369>:   je     0x7e2 <main(int, char**)+376>
   0x00000000000007dd <+371>:   call   0x540 <__stack_chk_fail@plt>
   0x00000000000007e2 <+376>:   leave 

Как вы можете видеть, в line 15 есть несколько инструкций, явно выдаваемых компилятором, потому что stack protector включен по умолчанию.

Если вы скомпилируете свою программу с помощью -fno-stack-protector, она не будет аварийно завершаться, [по крайней мере, это не происходит на моей машине с моим компилятором], но фактическое stack corruption будет там, просто производя непредсказуемые эффекты . В более крупной программе, когда происходит повреждение стека, вы можете ожидать любое странное поведение намного позже , чем в тот момент, когда произошло повреждение. Другими словами, защита стека - это очень хорошая вещь, которая помогает вам разоблачить проблему вместо сокрытия , то есть то, что естественно произойдет без него .

0 голосов
/ 11 января 2019

Вопрос в строке:

edgePoints[3] = edgePoint3;

Возможно, опечатка.

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

На этом этапе неопределенное поведение (упомянутая строка) уже разрушило хаос, и все может произойти. Из-за проверок, по крайней мере, вы видите, что у вас проблема со стеком.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...