Основная причина ошибки сегментации

Задний план

Я собрал qemu-system-x86_64.exe на компьютере с Windows, используя MSYS2 (x86_64), и я устраняю ошибку сегментации, которая возникает при попытке запустить его.
На самом деле я не думаю, что проблема связана с QEMU или MSYS2, это проблема отладки ошибки сегментации и, возможно, неправильной генерации кода.

Отладка ошибки сегментации

Программа вылетает с ошибкой segmentation fault в самом начале.
При запуске с gdb обнаружил следующее:

Starting program: C:\msys64\home\Administrator\qemu\x86_64-softmmu\qemu-system-x86_64.exe
[New Thread 4656.0x1194]

Program received signal SIGSEGV, Segmentation fault.
0x00000000007d3254 in getpagesize () at util/oslib-win32.c:535
535     {

(gdb) bt
#0  0x00000000007d3254 in getpagesize () at util/oslib-win32.c:535
#1  0x000000000086dd39 in init_real_host_page_size () at util/pagesize.c:16
#2  0x00000000007ea1b2 in __do_global_ctors ()
    at C:/repo/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/gccmain.c:67
#3  0x00000000007ea20f in __main ()
    at C:/repo/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/gccmain.c:83
#4  0x000000000040137f in __tmainCRTStartup ()
    at C:/repo/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crtexe.c:329
#5  0x00000000004014db in WinMainCRTStartup ()
    at C:/repo/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crtexe.c:195

Это странно.
Программа аварийно завершает работу при запуске __do_global_ctors и вызове init_real_host_page_size(), который вызывает getpagesize(). Это действительно простые функции:

uintptr_t qemu_real_host_page_size;
intptr_t qemu_real_host_page_mask;

static void __attribute__((constructor)) init_real_host_page_size(void)
{
    qemu_real_host_page_size = getpagesize();
    qemu_real_host_page_mask = -(intptr_t)qemu_real_host_page_size;
}

...

int getpagesize(void)
{
    SYSTEM_INFO system_info;

    GetSystemInfo(&system_info);
    return system_info.dwPageSize;
}

getpagesize() вылетает в самом начале функции, еще до того, как вызовет GetSystemInfo.
Вот дизассемблирование этого фрагмента кода и значения регистров:

(gdb) disassem
Dump of assembler code for function getpagesize:
   0x00000000007d3250 <+0>:     sub    $0x68,%rsp
=> 0x00000000007d3254 <+4>:     mov    %fs:0x0,%rax
   0x00000000007d325d <+13>:    mov    %rax,0x58(%rsp)
   0x00000000007d3262 <+18>:    xor    %eax,%eax
   0x00000000007d3264 <+20>:    lea    0x20(%rsp),%rcx
   0x00000000007d3269 <+25>:    callq  *0x68e8b9(%rip)        # 0xe61b28 <__imp_GetSystemInfo>
   0x00000000007d326f <+31>:    mov    0x24(%rsp),%eax
   0x00000000007d3273 <+35>:    mov    0x58(%rsp),%rdx
   0x00000000007d3278 <+40>:    xor    %fs:0x0,%rdx
   0x00000000007d3281 <+49>:    jne    0x7d3288 <getpagesize+56>
   0x00000000007d3283 <+51>:    add    $0x68,%rsp
   0x00000000007d3287 <+55>:    retq
   0x00000000007d3288 <+56>:    callq  0x85bde0 <__stack_chk_fail>
   0x00000000007d328d <+61>:    nop
End of assembler dump.
(gdb) info registers
rax            0x6f4b868           116701288
rbx            0x86ec10            8842256
rcx            0x6f4b8b8           116701368
rdx            0xe5a780            15050624
rsi            0x86e220            8839712
rdi            0x6f4ad50           116698448
rbp            0x6f4ad10           0x6f4ad10
rsp            0x22fd80            0x22fd80
r8             0x0                 0
r9             0x0                 0
r10            0x5000016b          1342177643
r11            0x22f9d8            2292184
r12            0x0                 0
r13            0x10                16
r14            0x0                 0
r15            0x0                 0
rip            0x7d3254            0x7d3254 <getpagesize+4>
eflags         0x10202             [ IF RF ]
cs             0x33                51
ss             0x2b                43
ds             0x2b                43
es             0x2b                43
fs             0x53                83
gs             0x2b                43

Похоже, что-то не так с доступом к памяти mov %fs:0x0,%rax.
Кто ставит FS на 83?

(gdb) starti
Starting program: C:\msys64\home\Administrator\qemu\x86_64-softmmu\qemu-system-x86_64.exe
[New Thread 3508.0x14b0]

Program stopped.
0x00000000778b6fb1 in ntdll!CsrSetPriorityClass ()
   from C:\Windows\SYSTEM32\ntdll.dll
(gdb) p $fs
$1 = 83
(gdb) watch $fs
Watchpoint 1: $fs
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x00000000007d3254 in getpagesize () at util/oslib-win32.c:535
535     {

ФС никто не ставит!

Вопросы

  • Сгенерированный GCC код, использующий неинициализированный регистр. Что может быть причиной этого? Был ли какой-то код инициализации, который должен был запуститься, но не запустился?
  • Любые идеи, как я могу отладить эту проблему?

person Amir Gonnen    schedule 18.11.2018    source источник
comment
установить точку останова (инструкцию br) для каждой основной функции в коде. Включение 'main' перед входом в прогон (инструкция r). Проверьте fs в каждой точке останова, чтобы точно определить, когда эта переменная установлена.   -  person user3629249    schedule 19.11.2018
comment
опубликованная трассировка вывода gdb показывает, что код выполнялся некоторое время, прежде чем был остановлен. Так что установка fs давно прошла   -  person user3629249    schedule 19.11.2018
comment
@user3629249 user3629249 Посмотрите вывод gdb. Сначала я запускаю команду starti, которая запускает код, но останавливается на первой инструкции. Затем я установил точку наблюдения для FS. С этого момента каждое изменение в FS будет останавливать прогон. Затем я выдаю команду c. gdb запускает код, но проверяет каждую инструкцию, которая выполняется, на предмет изменения FS. Требуется некоторое время, пока он не достигнет ошибки сегментации. Тот факт, что gdb не остановился в точке наблюдения до ошибки сегментации, доказывает, что FS не изменилась с начала выполнения до ошибки.   -  person Amir Gonnen    schedule 19.11.2018
comment
регистр fs является одним из сегментных регистров. он устанавливается перед запуском программы   -  person user3629249    schedule 20.11.2018


Ответы (1)


FS — это сегментный регистр x86. Обычно они не устанавливаются пользовательской программой, а устанавливаются ОС или библиотеками времени выполнения для различных специальных целей. Например, в Windows x86-64 GS используется для указания на блок данных для каждого потока: https://en.wikipedia.org/wiki/Win32_Thread_Information_Block (и FS не используется).

В данном случае проблема заключается в ошибке используемого вами компилятора GCC 8: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86832

В некоторых ситуациях этот компилятор генерирует код, предполагающий, что FS настроен для «собственного TLS», что неверно, поскольку MINGW не поддерживает «собственный TLS», а для FS не установлено ничего полезного.

Обходной путь — избегать компиляции с параметром компилятора -fstack-protector-strong. Для QEMU вы можете сделать это, передав флаг configure --disable-stack-protector.

(PS: если вы хотите знать, как я определил причину этого segfault: я погуглил «qemu-devel sigsegv getpagesize», что вызывает ветку списка рассылки, где кто-то еще столкнулся и сообщил об ошибке, проблема была диагностирована и ссылка на найденную ошибку GCC.)

person Peter Maydell    schedule 19.11.2018
comment
Вы правы, конечно. Я должен был потратить больше времени на поиск ответа в Google, а не на его отладку... Спасибо. - person Amir Gonnen; 19.11.2018
comment
Ты гораздо лучший гуглер, чем я. Я не знал, как это найти. Спасибо. - person nelsonjchen; 23.12.2018