Jak analizować plik zrzutu pamięci programu za pomocą GDB, gdy ma on parametry wiersza polecenia?

156

Mój program działa tak:

exe -p param1 -i param2 -o param3

Rozbił się i wygenerowany plik zrzutu pamięci, core.pid.

Chcę przeanalizować plik zrzutu pamięci wg

gdb ./exe -p param1 -i param2 -o param3 core.pid

Ale GDB rozpoznaje parametry pliku EXE jako dane wejściowe GDB.

Jak przeanalizować plik zrzutu pamięci w tej sytuacji?

Treper
źródło
1
Czy na pewno exenie jest to skrypt powłoki (do ustawiania niektórych zmiennych itp.), Jak np. W firefoxsystemie Linux?
Basile Starynkevitch

Odpowiedzi:

182

Możesz używać jądra z GDB na wiele sposobów, ale przekazywanie parametrów, które mają być przekazane do pliku wykonywalnego do GDB, nie jest sposobem na użycie pliku core. Może to być również powód wystąpienia tego błędu. Możesz użyć pliku podstawowego na następujące sposoby:

gdb <executable> <core-file>lub gdb <executable> -c <core-file>lub

gdb <executable>
...
(gdb) core <core-file>

Używając pliku core nie musisz przekazywać argumentów. Scenariusz awarii jest wyświetlany w GDB (sprawdzany z GDB w wersji 7.1 na Ubuntu).

Na przykład:

$ ./crash -p param1 -o param2
Segmentation fault (core dumped)
$ gdb ./crash core
GNU gdb (GDB) 7.1-ubuntu
...
Core was generated by `./crash -p param1 -o param2'. <<<<< See this line shows crash scenario
Program terminated with signal 11, Segmentation fault.
#0  __strlen_ia32 () at ../sysdeps/i386/i686/multiarch/../../i586/strlen.S:99
99    ../sysdeps/i386/i686/multiarch/../../i586/strlen.S: No such file or directory.
    in ../sysdeps/i386/i686/multiarch/../../i586/strlen.S
(gdb)

Jeśli chcesz przekazać parametry do pliku wykonywalnego w celu debugowania w GDB, użyj --args.

Na przykład:

$ gdb --args ./crash -p param1 -o param2
GNU gdb (GDB) 7.1-ubuntu
...
(gdb) r
Starting program: /home/@@@@/crash -p param1 -o param2

Program received signal SIGSEGV, Segmentation fault.
__strlen_ia32 () at ../sysdeps/i386/i686/multiarch/../../i586/strlen.S:99
99    ../sysdeps/i386/i686/multiarch/../../i586/strlen.S: No such file or directory.
    in ../sysdeps/i386/i686/multiarch/../../i586/strlen.S
(gdb)

Strony podręcznika będą pomocne przy przeglądaniu innych opcji GDB.

inny.anon.coward
źródło
38

Proste użycie GDB do debugowania plików coredump:

gdb <executable_path> <coredump_file_path>

Plik coredump dla „procesu” jest tworzony jako plik „core.pid”.

Po wejściu do znaku zachęty GDB (po wykonaniu powyższego polecenia) wpisz:

...
(gdb) where

W ten sposób uzyskasz informacje o stosie, w którym możesz przeanalizować przyczynę awarii / usterki. Innym poleceniem do tych samych celów jest:

...
(gdb) bt full

To jest to samo, co powyżej. Zgodnie z konwencją wyświetla wszystkie informacje o stosie (co ostatecznie prowadzi do lokalizacji awarii).

parasrish
źródło
22

Po prostu pomiń parametry. GDB ich nie potrzebuje:

gdb ./exe core.pid
Nialscorva
źródło
Ale to nie działa. Ostrzeżenie wyjściowe gdb: plik core może nie pasować do określonego pliku wykonywalnego. Nie udało się odczytać prawidłowego obrazu pliku obiektu z pamięci.
Treper
6
„Plik podstawowy może nie pasować do określonego pliku wykonywalnego”. Czy zmodyfikowałeś exe po tym, jak wyprodukował rdzeń? Czy może odbudowałeś go z różnymi opcjami wiersza poleceń? Bardzo ważne jest, aby dać GDB dokładnie ten sam plik binarny, który wyprodukował rdzeń. Jeśli tego nie zrobisz, wyrzucisz śmieci.
Zatrudniony w Rosji
2
Upewnij się również, że plik binarny przekazywany do gdb nie jest usuwany. Możesz uruchomić plik <nazwa pliku binarnego>, który pokazuje, że jest usunięty lub nie.
Diwakar Sharma
12

objdump+ gdbminimalny przykład do uruchomienia

TL; DR:

A teraz pełna konfiguracja testu edukacyjnego:

main.c

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int myfunc(int i) {
    *(int*)(NULL) = i; /* line 7 */
    return i - 1;
}

int main(int argc, char **argv) {
    /* Setup some memory. */
    char data_ptr[] = "string in data segment";
    char *mmap_ptr;
    char *text_ptr = "string in text segment";
    (void)argv;
    mmap_ptr = (char *)malloc(sizeof(data_ptr) + 1);
    strcpy(mmap_ptr, data_ptr);
    mmap_ptr[10] = 'm';
    mmap_ptr[11] = 'm';
    mmap_ptr[12] = 'a';
    mmap_ptr[13] = 'p';
    printf("text addr: %p\n", text_ptr);
    printf("data addr: %p\n", data_ptr);
    printf("mmap addr: %p\n", mmap_ptr);

    /* Call a function to prepare a stack trace. */
    return myfunc(argc);
}

Skompiluj i uruchom, aby wygenerować rdzeń:

gcc -ggdb3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
ulimit -c unlimited
rm -f core
./main.out

Wynik:

text addr: 0x4007d4
data addr: 0x7ffec6739220
mmap addr: 0x1612010
Segmentation fault (core dumped)

GDB wskazuje nam dokładną linię, w której wystąpił błąd segmentacji, czego większość użytkowników chce podczas debugowania:

gdb -q -nh main.out core

następnie:

Reading symbols from main.out...done.
[New LWP 27479]
Core was generated by `./main.out'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000000000400635 in myfunc (i=1) at main.c:7
7           *(int*)(NULL) = i;
(gdb) bt
#0  0x0000000000400635 in myfunc (i=1) at main.c:7
#1  0x000000000040072b in main (argc=1, argv=0x7ffec6739328) at main.c:28

co wskazuje nam bezpośrednio linię buggy 7.

Argumenty CLI są przechowywane w pliku podstawowym i nie muszą być ponownie przekazywane

Aby odpowiedzieć na konkretne pytania dotyczące argumentów CLI, widzimy, że jeśli zmienimy argumenty CLI, np. Za pomocą:

rm -f core
./main.out 1 2

to zostanie odzwierciedlone w poprzednim bactrace bez żadnych zmian w naszych poleceniach:

Reading symbols from main.out...done.
[New LWP 21838]
Core was generated by `./main.out 1 2'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000564583cf2759 in myfunc (i=3) at main.c:7
7           *(int*)(NULL) = i; /* line 7 */
(gdb) bt
#0  0x0000564583cf2759 in myfunc (i=3) at main.c:7
#1  0x0000564583cf2858 in main (argc=3, argv=0x7ffcca4effa8) at main.c:2

Więc zwróć uwagę, jak teraz argc=3. Dlatego musi to oznaczać, że plik podstawowy przechowuje te informacje. Zgaduję, że po prostu przechowuje to jako argumenty main, tak jak przechowuje argumenty innych funkcji.

Ma to sens, jeśli weźmie się pod uwagę, że zrzut pamięci musi przechowywać całą pamięć i stan rejestru programu, a więc zawiera wszystkie informacje potrzebne do określenia wartości argumentów funkcji na bieżącym stosie.

Mniej oczywiste jest to, jak sprawdzić zmienne środowiskowe: Jak uzyskać zmienną środowiskową ze zrzutu pamięci Zmienne środowiskowe są również obecne w pamięci, więc objdump zawiera te informacje, ale nie jestem pewien, jak wygodnie wyświetlić je wszystkie za jednym razem , jeden po drugim działał na moich testach:

p __environ[0]

Analiza Binutils

Używając narzędzi binutils, takich jak readelfi objdump, możemy zbiorczo zrzucić informacje zawarte w corepliku, takie jak stan pamięci.

Większość / całość musi być również widoczna przez GDB, ale te narzędzia binutils oferują bardziej masowe podejście, które jest wygodne w niektórych przypadkach, podczas gdy GDB jest wygodniejszy do bardziej interaktywnej eksploracji.

Pierwszy:

file core

mówi nam, że coreplik jest w rzeczywistości plikiem ELF :

core: ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style, from './main.out'

dlatego jesteśmy w stanie sprawdzić go bardziej bezpośrednio za pomocą zwykłych narzędzi binutils.

Szybkie spojrzenie na standard ELF pokazuje, że w rzeczywistości istnieje typ ELF do niego dedykowany:

Elf32_Ehd.e_type == ET_CORE

Więcej informacji o formacie można znaleźć pod adresem:

man 5 core

Następnie:

readelf -Wa core

podaje kilka wskazówek dotyczących struktury plików. Wydaje się, że pamięć jest zawarta w zwykłych nagłówkach programów:

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  NOTE           0x000468 0x0000000000000000 0x0000000000000000 0x000b9c 0x000000     0
  LOAD           0x002000 0x0000000000400000 0x0000000000000000 0x001000 0x001000 R E 0x1000
  LOAD           0x003000 0x0000000000600000 0x0000000000000000 0x001000 0x001000 R   0x1000
  LOAD           0x004000 0x0000000000601000 0x0000000000000000 0x001000 0x001000 RW  0x1000

a w obszarze notatek znajduje się więcej metadanych, w szczególności prstatuskomputer :

Displaying notes found at file offset 0x00000468 with length 0x00000b9c:
  Owner                 Data size       Description
  CORE                 0x00000150       NT_PRSTATUS (prstatus structure)
  CORE                 0x00000088       NT_PRPSINFO (prpsinfo structure)
  CORE                 0x00000080       NT_SIGINFO (siginfo_t data)
  CORE                 0x00000130       NT_AUXV (auxiliary vector)
  CORE                 0x00000246       NT_FILE (mapped files)
    Page size: 4096
                 Start                 End         Page Offset
    0x0000000000400000  0x0000000000401000  0x0000000000000000
        /home/ciro/test/main.out
    0x0000000000600000  0x0000000000601000  0x0000000000000000
        /home/ciro/test/main.out
    0x0000000000601000  0x0000000000602000  0x0000000000000001
        /home/ciro/test/main.out
    0x00007f8d939ee000  0x00007f8d93bae000  0x0000000000000000
        /lib/x86_64-linux-gnu/libc-2.23.so
    0x00007f8d93bae000  0x00007f8d93dae000  0x00000000000001c0
        /lib/x86_64-linux-gnu/libc-2.23.so
    0x00007f8d93dae000  0x00007f8d93db2000  0x00000000000001c0
        /lib/x86_64-linux-gnu/libc-2.23.so
    0x00007f8d93db2000  0x00007f8d93db4000  0x00000000000001c4
        /lib/x86_64-linux-gnu/libc-2.23.so
    0x00007f8d93db8000  0x00007f8d93dde000  0x0000000000000000
        /lib/x86_64-linux-gnu/ld-2.23.so
    0x00007f8d93fdd000  0x00007f8d93fde000  0x0000000000000025
        /lib/x86_64-linux-gnu/ld-2.23.so
    0x00007f8d93fde000  0x00007f8d93fdf000  0x0000000000000026
        /lib/x86_64-linux-gnu/ld-2.23.so
  CORE                 0x00000200       NT_FPREGSET (floating point registers)
  LINUX                0x00000340       NT_X86_XSTATE (x86 XSAVE extended state)

objdump może łatwo zrzucić całą pamięć za pomocą:

objdump -s core

który zawiera:

Contents of section load1:

 4007d0 01000200 73747269 6e672069 6e207465  ....string in te
 4007e0 78742073 65676d65 6e740074 65787420  xt segment.text 

Contents of section load15:

 7ffec6739220 73747269 6e672069 6e206461 74612073  string in data s
 7ffec6739230 65676d65 6e740000 00a8677b 9c6778cd  egment....g{.gx.

Contents of section load4:

 1612010 73747269 6e672069 6e206d6d 61702073  string in mmap s
 1612020 65676d65 6e740000 11040000 00000000  egment..........

który dokładnie pasuje do wartości stdout w naszym przebiegu.

Zostało to przetestowane na Ubuntu 16.04 amd64, GCC 6.4.0 i binutils 2.26.1.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
10

Z samouczka dotyczącego debuggera GDB RMS :

prompt > myprogram
Segmentation fault (core dumped)
prompt > gdb myprogram
...
(gdb) core core.pid
...

Upewnij się, że plik naprawdę jest coreobrazem - sprawdź go za pomocą file.

Brian Cain
źródło
9

Nieco inne podejście pozwoli Ci całkowicie pominąć GDB. Jeśli chcesz tylko śledzenia wstecznego, specyficzne dla Linuksa narzędzie „catchsegv” przechwyci SIGSEGV i wyświetli ślad.

Rob Latham
źródło
3

Nie ma znaczenia, czy plik wykonywalny ma argumenty, czy nie. Aby uruchomić GDB na dowolnym pliku binarnym z wygenerowanym plikiem core, składnia jest poniżej.

Syntax:
gdb <binary name> <generated core file>
Eg:
gdb l3_entity 6290-corefile

Dla lepszego zrozumienia przedstawię poniższy przykład.

bash-4.1$ **gdb l3_entity 6290-corefile**

**Core was generated** by `/dir1/dir2/dir3/l3_entity **Program terminated with signal SIGABRT, Aborted.**
#0
#1
#2
#3
#4
#5
#6
#7
#8
#9
#10
(gdb)

Z powyższego wyniku można się domyślić, co dotyczy jądra, czy jest to dostęp NULL, SIGABORT itp.

Te liczby od 0 do 10 to ramki stosu GDB. Te ramki stosu nie należą do Twojego pliku binarnego. W powyższych klatkach 0-10, jeśli podejrzewasz, że coś jest nie tak, wybierz tę klatkę

(gdb) frame 8

Teraz, aby zobaczyć więcej szczegółów na ten temat:

(gdb) list +

Aby dokładniej zbadać problem, możesz teraz wydrukować podejrzane wartości zmiennych tutaj.

(gdb) print thread_name
Venkatakrishna Kalepalli
źródło
0

Po prostu wpisz polecenie:

$ gdb <Binary> <codeDump>

Lub

$ gdb <binary>

$ gdb) core <coreDump>

Nie ma potrzeby podawania żadnego argumentu wiersza poleceń. Zrzut kodu wygenerowany w wyniku wcześniejszego ćwiczenia.

KB Arora
źródło
-1

Możesz przeanalizować plik zrzutu pamięci za pomocą polecenia „gdb”.

 gdb - The GNU Debugger

 syntax:

 # gdb executable-file core-file

 example: # gdb out.txt core.xxx 
Ranjithkumar T
źródło
1
out.txt to plik wykonywalny? Wydaje się, że to mylące rozszerzenie pliku.
Alan