Подтвердить что ты не робот

Получить адрес, вызвавший ошибку сегментации из дампа ядра, используя C

Я пытаюсь написать программу на языке C, которая может анализировать основные файлы дампа. Мой вопрос: как я могу получить адрес, который вызвал основной дамп в C? Я знаю, что можно получить адрес, используя gdb из этого ответа:

Как я могу заставить GDB рассказать мне, какой адрес вызвал segfault?

Но я хотел бы непосредственно получить адрес в C. Любая информация была бы высоко оценена. Спасибо!

Примечание: Я знаю, как анализировать основной дамп как эльф. Но я не знаю, как получить адрес, вызвавший segfault.

4b9b3361

Ответ 1

Мой вопрос в том, как я могу получить адрес, который вызвал основной дамп  в C?

Краткий ответ:

Есть два способа интерпретировать этот вопрос.

  • Каков был адрес инструкции о неисправности?

  • Каким был адрес, который был за пределами границ?

Откидные отвалы эльфов хранят всю метаинформацию в заметках, которые сохраненный в сегменте примечаний. Примечания имеют разные типы.

Чтобы ответить на # 1, нам нужно захватить регистры. Посмотрите на заголовок эльфа, чтобы найдите таблицу заголовков программ. Пройдите таблицу заголовков программ, чтобы найти таблица примечаний (тип PT_NOTE). Пройдите стол для заметок, чтобы найти примечание тип NT_PRSTATUS. Полезная нагрузка этой заметки - struct elf_prstatus, которую можно найти в linux/elfcore.h. Один из поля этой структуры - все регистры общего назначения. грейфер % rip, и все готово.

Для # 2 мы делаем что-то подобное. На этот раз мы ищем записку типа NT_SIGINFO. Полезной нагрузкой этой заметки является структура siginfo_t определенный в сигнале .h. Для применимых сигналов (SIGILL, SIGFPE, SIGSEGV, SIGBUS), поле si_addr будет содержать адрес, который вы пытались доступ, но не мог.

Более подробная информация приведена ниже. В примере core dump, rip - 0x400560, адрес инструкции, который пытался сделать незаконный доступ. Это отображается вместе с остальными регистрами общего назначения.

Память, которую программа пыталась получить, равна 0x03. Это отображается вместе с остальной информацией о сигнале.

Длинный ответ:

Я думаю, что BFD имеет 25-летнюю рывку, поэтому я бы не использовал его просто для того, чтобы сбрасывать содержимое основного файла в ящик Linux. Возможно, если бы вам пришлось написать какой-то код общего назначения, который должен работать с кучей форматов, но даже тогда я не уверен, что сегодня я пойду.

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

Я написал небольшую программу для захвата регистров из основного файла x86_68 и печати некоторых метаданных. Я положил его на github. Логика получения полезной нагрузки примечания находится в этой функции:

void *get_note(void *vp, int nt_type){
    Elf64_Ehdr *eh=vp;
    for(int i=0; i<eh->e_phnum; ++i){
        Elf64_Phdr *ph=(vp+eh->e_phoff+i*eh->e_phentsize);
        if(ph->p_type!=PT_NOTE){
            continue;
        }
        void *note_table=(vp + ph->p_offset);
        void *note_table_end=(note_table+ph->p_filesz);
        Elf64_Nhdr *current_note=note_table;
        while(current_note<(Elf64_Nhdr *)note_table_end){
            void *note_end=current_note;
            note_end += 3*sizeof(Elf64_Word);
            note_end += roundup8(current_note->n_namesz);
            if(current_note->n_type==nt_type){
                return note_end;
            }
            note_end += roundup8(current_note->n_descsz);
            current_note=note_end;          
        }
    }
    return 0;
}

Функция передается указателем на файл elf и тип заметки и возвращает указатель на полезную нагрузку связанной ноты, если она существует. Различные возможные типы нот находятся в elf.h. Типы заметки, которые я фактически вижу в основных файлах на моей машине:

#define NT_PRSTATUS 1       /* Contains copy of prstatus struct */
#define NT_FPREGSET 2       /* Contains copy of fpregset struct */
#define NT_PRPSINFO 3       /* Contains copy of prpsinfo struct */
#define NT_AUXV     6       /* Contains copy of auxv array */
#define NT_X86_XSTATE   0x202       /* x86 extended state using xsave */
#define NT_SIGINFO  0x53494749  /* Contains copy of siginfo_t,
                                   size might increase */
#define NT_FILE     0x46494c45  /* Contains information about mapped
                                   files */

Большинство этих структур находятся в заголовках под /usr/include/linux. Структура xsave представляет собой пару КБ информации с плавающей точкой, описанную в Ch 13 руководства intel. В нем записаны SSE, AVX и MPX.

Полезная нагрузка NT_FILE, похоже, не имеет связанной структуры в заголовке, но она описана в комментарии ядра (fs/binfmt_elf.c):

/*
 * Format of NT_FILE note:
 *
 * long count     -- how many files are mapped
 * long page_size -- units for file_ofs
 * array of [COUNT] elements of
 *   long start
 *   long end
 *   long file_ofs
 * followed by COUNT filenames in ASCII: "FILE1" NUL "FILE2" NUL...
 */

Изменения для анализа файла эльфа для 32-битной системы довольно тривиальны. Используйте соответствующие структуры Elf32_XXX и округлите до 4 вместо 8 для полей с переменным размером.

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

 $ ./read_pc -biprst core
General Registers: 
r15     0x000000000000000000  r14     0x000000000000000000  
r13     0x0000007ffc20d36a50  r12     0x000000000000400430  
rbp     0x0000007ffc20d36950  rbx     0x000000000000000000  
r11     0x000000000000000246  r10     0x000000000000000000  
r9      0x000000000000000002  r8      0x000000000000000000  
rax     0x000000000000000003  rcx     0x00000000007ffffffe  
rdx     0x0000007f5817523780  rsi     0x000000000000000001  
rdi     0x000000000000000001  ss      0x00000000000000002b  
rip     0x000000000000400560  cs      0x000000000000000033  
eflags  0x000000000000010246  rsp     0x0000007ffc20d36950  
fs_base 0x0000007f5817723700  gs_base 0x000000000000000000  
ds      0x000000000000000000  es      0x000000000000000000  
fs      0x000000000000000000  gs      0x000000000000000000  
orig_rax 0x00ffffffffffffffff  

Program status: 
signo 11 signal code 0 errno 0
cursig 11 sigpend 000000000000000000 sigheld 000000000000000000
pid 27547 ppid 26600 pgrp 27547 sid 26600
utime: 0.000000 stime 0.000000
cutime: 0.000000 cstime 0.000000
fpvalid: 1


Signal Information: 
signo: 11 errno 0 code 1
addr 0x3 addr_lsb 0 addr_bnd ((nil), (nil))


Process Information:
state 0 (R) zombie 0 nice 0 flags 0x400600
uid 1000 gid 1000 pid 27547 ppid 26600 pgrp 27547 sid 26600
fname: foo
args: ./foo 


Backtrace: 
rip = 0x000000000000400560
rip = 0x000000000000400591
rip = 0x0000000000004005a1


Program Headers:
   Type      Offset             Virt Addr          PhysAddr          
             FileSiz            MemSize              Flags  Align    
 NOTE      0x00000000000004a0 0x0000000000000000 0000000000000000
           0x0000000000000b98 0x0000000000000000         0x000000
 LOAD      0x0000000000002000 0x0000000000400000 0000000000000000
           0x0000000000001000 0x0000000000001000 R X     0x001000
 LOAD      0x0000000000003000 0x0000000000600000 0000000000000000
           0x0000000000001000 0x0000000000001000   X     0x001000
 LOAD      0x0000000000004000 0x0000000000601000 0000000000000000
           0x0000000000001000 0x0000000000001000  WX     0x001000
 LOAD      0x0000000000005000 0x00000000018bf000 0000000000000000
           0x0000000000021000 0x0000000000021000  WX     0x001000
 LOAD      0x0000000000026000 0x00007f581715e000 0000000000000000
           0x0000000000001000 0x00000000001c0000 R X     0x001000
 LOAD      0x0000000000027000 0x00007f581731e000 0000000000000000
           0x0000000000000000 0x00000000001ff000         0x001000
 LOAD      0x0000000000027000 0x00007f581751d000 0000000000000000
           0x0000000000004000 0x0000000000004000   X     0x001000
 LOAD      0x000000000002b000 0x00007f5817521000 0000000000000000
           0x0000000000002000 0x0000000000002000  WX     0x001000
 LOAD      0x000000000002d000 0x00007f5817523000 0000000000000000
           0x0000000000004000 0x0000000000004000  WX     0x001000
 LOAD      0x0000000000031000 0x00007f5817527000 0000000000000000
           0x0000000000001000 0x0000000000026000 R X     0x001000
 LOAD      0x0000000000032000 0x00007f5817722000 0000000000000000
           0x0000000000003000 0x0000000000003000  WX     0x001000
 LOAD      0x0000000000035000 0x00007f581774a000 0000000000000000
           0x0000000000002000 0x0000000000002000  WX     0x001000
 LOAD      0x0000000000037000 0x00007f581774c000 0000000000000000
           0x0000000000001000 0x0000000000001000   X     0x001000
 LOAD      0x0000000000038000 0x00007f581774d000 0000000000000000
           0x0000000000001000 0x0000000000001000  WX     0x001000
 LOAD      0x0000000000039000 0x00007f581774e000 0000000000000000
           0x0000000000001000 0x0000000000001000  WX     0x001000
 LOAD      0x000000000003a000 0x00007ffc20d16000 0000000000000000
           0x0000000000022000 0x0000000000022000  WX     0x001000
 LOAD      0x000000000005c000 0x00007ffc20d9c000 0000000000000000
           0x0000000000002000 0x0000000000002000   X     0x001000
 LOAD      0x000000000005e000 0x00007ffc20d9e000 0000000000000000
           0x0000000000002000 0x0000000000002000 R X     0x001000
 LOAD      0x0000000000060000 0xffffffffff600000 0000000000000000
           0x0000000000001000 0x0000000000001000 R X     0x001000
All worked

Ответ 2

Существует парсер ELF, предоставляемый библиотекой BFD (Двоичный файловый дескриптор), которая является частью binutils и используется gdb, readelf и другие. Однако, по-видимому, он довольно старый и жестокий, поэтому может быть проще написать собственный парсер ELF непосредственно из спецификации.

Библиотека времени выполнения обычно устанавливает обработчик сигнала для обнаружения ошибок (например, SIGSEV, SIGBUS и т.д.) и abort. Чтобы получить адрес ошибки, вам, скорее всего, понадобится раскрутить стек, чтобы сделать обратную линию. Вам также потребуется иметь таблицу символов для поиска адресов, соответствующих именам функций. Это доступно либо как часть двоичного файла (в сборке отладки), либо отдельный файл таблицы символов. Адрес неисправности, который вы используете, - _siginfo._sifields._sigfault.si_addr.

Кажется, что объект siginfo не хранится в основных файлах. источник ядра для do_coredump() стоит посмотреть. Но сохранение siginfo кажется чем-то, над чем люди работают.

@evaitl дает отличный ответ выше, поэтому мой голос идет туда.:)

Дальнейшее чтение: