TL;DR
Gdb-Python-Erweiterung und pyelftools
-Modul Ich habe es verwendet, um Backtrace zu implementieren.
Dies kann in Umgebungen nützlich sein, in denen der Befehl gdb backtrace
nicht so funktioniert, wie er ist.
Bei der Entwicklung bei Kumiko Michiho,
Ist das nicht die Situation, die selten vorkommt? Als ich in Schwierigkeiten geriet, weil ich dank solcher Debug-Informationen (https://qiita.com/mhiramat/items/8df17f5113434e93ff0c) mit solchen Serien und LEDs in die Savanne geworfen wurde, wurde ich ein Freund, der Bagujoho las. Jetzt, wo ich daran gewöhnt bin, möchte ich es zurückverfolgen.
Der Einfachheit halber habe ich den Retest in der Umgebung von x64 Ubuntu 16.04 gcc5.4 / python3.5 anstelle der eingebetteten Umgebung [^ 1] bestätigt.
[^ 1]: Als eingebautes Gerät haben wir es in einen tatsächlichen Kampf mit RX gebracht.
Die Grundlagen von DWARF werden ausführlich in So gehen Sie Debug-Informationen durchgehen erläutert. Auch [hier](https://ja.osdn.net/projects/drdeamon64/wiki/DWARF%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3% Der Artikel 83% 95% E3% 82% A9% E3% 83% BC% E3% 83% 9E% E3% 83% 83% E3% 83% 88) war ebenfalls sehr hilfreich.
Außerdem werde ich nicht zu sehr auf gdb Python-API eingehen. Ich mache keine komplizierten Dinge mit dem Python-GDB-Modul, daher denke ich, dass Sie es verstehen können, wenn Sie sich das Beispiel ansehen.
Es ist schwierig, DWARF mit bloßen Händen zu dekodieren. Installieren Sie daher das Python-Modul pyelftools, das einen erheblichen Teil dekodiert. Ich möchte die Umgebung mit virtualenv usw. isolieren, aber als ich Python über gdb aufrief, wurde der Modulpfad in virtualenv nicht angezeigt, sodass ich ihn direkt in das System einfügte.
$ sudo pip3 install pyelftools
Alternativ können Sie das Modul irgendwo erweitern und am Anfang des Python-Skripts platzieren
import sys
sys.path.append("/home/oreore/virtualenv/python/site-packages")
Sie können den Pfad wie folgt eingeben [^ 2].
[^ 2]: Bitte weisen Sie darauf hin, ob es eine brillante Lösung gibt, die gdb überzeugen kann.
Verwenden Sie erschwinglichen Code, um zu sehen, wie es funktioniert. Hier verwenden wir die Mach-Komprimierungs- / Dekomprimierungsbibliothek lz4 und ihre Testanwendung simpleBuffer
. Zum Beispiel erklärt hier, was "lz4" ist.
Klonen und erstellen Sie nun wie folgt.
$ git clone https://github.com/lz4/lz4.git
$ cd lz4
$ CFLAGS="-g3 -fno-dwarf2-cfi-asm" make all
Dies ist eine kleine Ergänzung zum Build-Flag, aber in gcc (Ubuntu 5.4.1-2ubuntu1 ~ 16.04) 5.4.1 20160904 habe ich hier versucht, Gas-CFI-Direktive /as/CFI-directives.html) scheint den Abschnitt ".debug_frame" wegzulassen, in dem die Stapelrahmeninformationen gespeichert sind, und nur die vereinfachte Version (?) ".Eh_frame" wurde veröffentlicht.
pyelftools
unterstützt auch das Decodieren von .eh_frame
, die Verwendung dauert jedoch einige Zeit. Deshalb habe ich diesmal den Compiler gebeten, ".debug_frame" (Option "-fno-dwarf2-cfi-asm") auszugeben (https://gcc.gnu.org/onlinedocs/gcc/Debugging-Options). Ich habe es mit .html angefordert). Dies hat sich möglicherweise mit Ubuntu / gcc geändert.
Führen Sie es zuerst mit gdb aus, brechen Sie an einer geeigneten Stelle ab und verfolgen Sie es zurück.
$ cd examples
$ gdb simpleBuffer
:
:
(gdb) b lz4.c:578
Breakpoint 1 at 0x4010a4: lz4.c:578. (11 locations)
(gdb) r
Starting program: /home/oreore/lz4/examples/simpleBuffer
Breakpoint 1, LZ4_compress_generic (acceleration=1, dictIssue=<optimized out>, dict=<optimized out>, tableType=<optimized out>,
outputLimited=<optimized out>, maxOutputSize=<optimized out>, inputSize=57, dest=0x61e010 "",
source=0x41b4b8 "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", cctx=0x7fffffff9810) at lz4.c:578
578 LZ4_putPosition(ip, cctx->hashTable, tableType, base);
(gdb) bt
#0 LZ4_compress_generic (acceleration=1, dictIssue=<optimized out>, dict=<optimized out>,
tableType=<optimized out>, outputLimited=<optimized out>, maxOutputSize=<optimized out>,
inputSize=57, dest=0x61e010 "",
source=0x41b4b8 "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", cctx=0x7fffffff9570)
at lz4.c:578
#1 LZ4_compress_fast_extState (state=0x7fffffff9570,
sourpppce=0x41b4b8 "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", dest=0x61e010 "",
inputSize=57, maxOutputSize=73, acceleration=1) at lz4.c:739
#2 0x0000000000404722 in LZ4_compress_fast (
source=0x41b4b8 "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", dest=0x61e010 "",
inputSize=57, maxOutputSize=73, acceleration=1) at lz4.c:760
#3 0x0000000000404776 in LZ4_compress_default (
source=0x41b4b8 "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", dest=0x61e010 "",
inputSize=57, maxOutputSize=73) at lz4.c:771
#4 0x0000000000400957 in main () at simple_buffer.c:54
Lassen Sie uns dies reproduzieren.
Ich habe die fertige in [gist] vorbereitet (https://gist.github.com/sumomoneko/d8415b14f8eddf74a3eb9bd6d521fab3), daher möchte ich sie entsprechend erläutern.
Ich möchte sofort mit der Analyse von DWARF beginnen, aber vorher organisieren wir die Ausführungsumgebung.
Dieses Mal erstellen wir ein Python-Skript, das auf x64-GDB ausgeführt wird. Es ist jedoch nicht unbedingt erforderlich, es separat auf GDB auszuführen. Aufgrund der Natur von DWARF bedeutet dies nicht, dass es x64 sein muss. Wenn die folgenden Funktionen vorbereitet sind, kann sie unabhängig von GDB und Architektur als einzelnes Python-Skript ausgeführt werden.
Lassen Sie uns diese Funktionen zunächst als Funktionen trennen, damit wir das Ziel später problemlos ändern können. Die Implementierung jeder Funktion wird unten erläutert.
Ruft den Pfad der ausführbaren Datei ab, die nicht entfernt wurde. Wir rufen den Namen des Objekts ab, das unter gdb ausgeführt wird (in gdb als "minderwertig" bezeichnet). Wenn es integriert ist, handelt es sich um eine Pre-Strip-Binärdatei, die vor dem Brennen in das ROM gespeichert wurde.
def get_elf_file_name() -> str:
"""
Rufen Sie den Namen der Datei ab, die die Debug-Informationen enthält.
In der GDB-Umgebung wird GDB.objfiles()[0].Gefunden in Dateiname
:return:Dateipfad mit Debug-Informationen
"""
import gdb
return gdb.objfiles()[0].filename
uintptr_t ret = *reinterpret_cast<uintptr_t*>(addr);
Hier ist ein Bild davon mit gdb-python. Es wird verwendet, um den Speicher abzurufen, auf den das Register zeigt. Da der Stapelbereich als Speicherbereich benötigt wird, empfiehlt es sich, den Stapelbereich zumindest als Speicherauszugsziel festzulegen, wenn Sie einen eigenen Speicherauszug erstellen.
def read_uintptr_t(addr: int) -> int:
"""
uintptr_Lesen Sie Daten der Größe t aus dem Speicher
uintptr_t ret = *reinterpret_cast<uintptr_t*>(addr);
:param addr:Adresse
:return:Daten
"""
import gdb
uintptr_t = gdb.lookup_type('uintptr_t')
return int(gdb.Value(addr).reinterpret_cast(uintptr_t.pointer()).dereference())
Ich habe gerade (gdb) p $ pc
in python-gdb geschrieben.
Wenn Sie einen eigenen Speicherauszug erstellen, ist dies wie das Speichern der Quelladresse, die zur Ausnahmeverarbeitung gesprungen ist?
def get_pc() -> int:
"""
Holen Sie sich den Programmzähler
:return:PC-Wert
"""
import gdb
return int(gdb.parse_and_eval("$pc"))
DWARF wurde entwickelt, um eine Vielzahl von Architekturen zu unterstützen. Daher werden die Register nicht mit architekturspezifischen Namen behandelt, sondern nummeriert und verwaltet. Welches Register ist dann welche Nummer? Schnell [Quellcode von gdb](https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=blob;f=gdb Werfen wir einen Blick auf /amd64-tdep.c;h=b589d93940f1f498177ba91273190dc9b0714370;hb=HEAD#l156).
Hier ist also das fertige Produkt. Ich habe die Register "stN" und "mmN" auskommentiert, aber dies ist ein Durchhang, weil die breiten Register ein Ärger waren [^ 3]. Wenn Sie nach einer Registernummer für eine andere Architektur suchen, sollten Sie sich nach einer ähnlichen Datei mit einem CPU-Namen an einer ähnlichen Stelle im GDB-Quellbaum umsehen.
[^ 3]: Ursprünglich habe ich es zum Debuggen der eingebauten CPU geschrieben, daher habe ich kein solches Register.
def get_registers() -> List[int]:
"""
Sammeln Sie Registerwerte von gdb gemäß der DWARF-Registernummer
https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=blob;f=gdb/amd64-tdep.c;h=b589d93940f1f498177ba91273190dc9b0714370;hb=HEAD#l156
:return:Liste der Registerwerte in der Reihenfolge der DWARF-Registernummer
"""
import gdb
reg_names = ["rax", "rdx", "rcx", "rbx", "rsi", "rdi", "rbp", "rsp",
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
"rip",
# "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7",
# "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15",
None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None,
# "st0", "st1", "st3", "st4", "st5", "st6", "st7",
None, None, None, None, None, None, None, None,
# "mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7",
None, None, None, None, None, None, None, None,
"eflags",
"es", "cs", "ss", "ds", "fs", "gs", None, None,
None, None, None, None,
None, None,
"mxcsr", "fctrl", "fstat"]
ret = []
for name in reg_names:
if name is not None:
val = int(gdb.parse_and_eval("${}".format(name)))
ret.append(val)
else:
ret.append(-1)
return ret
Wenn Sie in den Aufrufbaum zurückkehren, müssen Sie den Stapel zurückspulen. Da das Register, das den Stapel enthält, architekturabhängig ist, wird es in Funktionen als Rückspulverarbeitung unterteilt.
def unwind_stack(regs: List[int], cfa: int) -> None:
"""
Stellen Sie die Adresse in das Register ein, in dem sich der Stapelzeiger befindet.
Für x64$Für rsp. Die DWARF-Registernummer ist 7, stellen Sie also die Adresse dort ein.
:param regs:Array von Registern(DWARF-Registernummernreihenfolge)
:param cfa:Adresse, die als Stapelzeiger festgelegt werden soll
:return: None
"""
regs[7] = cfa
Das war's für das Vertrauen in GDB und Architektur.
Der Gesamtfluss der Backtrace-Verarbeitung ist wie folgt.
Dies ist fest mit main ()
geschrieben.
def main() -> None:
"""
Rückverfolgung anzeigen
"""
with open(get_elf_file_name(), 'rb') as f:
elf_file = ELFFile(f)
if not elf_file.has_dwarf_info():
print('file has no DWARF info')
return
dwarf_info = elf_file.get_dwarf_info()
#Zum ersten Mal von der aktuellen Stopppositionsinformation
pc = get_pc()
regs = get_registers()
i = 0
while True:
#Funktionsinformationen der Stoppposition abrufen
fi = get_func_info(dwarf_info, pc)
if fi is None:
break
print("#{:<3}0x{:016x} in {}() at {}:{}".format(i,
fi["addr"],
fi["func_name"],
fi["path"],
fi["line"]))
i += 1
#Schauen Sie sich den Stapelrahmen an und folgen Sie dem Aufrufer
pc, regs = get_prev_frame(fi["cu"], pc, regs, read_uintptr_t)
if pc is None:
break
"Abrufen der Stoppadresse, des Registers (und des Stapelzeigers)" ruft nur die im vorherigen Abschnitt erläuterte Funktion auf. Wir werden sie daher in der Reihenfolge "Abrufen der Funktionsinformationen einschließlich der Adresse" betrachten.
Als DWARF-Erfassungsroute, um eine Funktion zu erhalten, die sie von einer bestimmten Adresse enthält,
--Lick alle CU / DIE und finde einen Treffer-Adressbereich --Subtrahiere DIE von der Adresse
Es gibt zwei Möglichkeiten. Letzteres ist genau die Methode für diesen Zweck, [DWARFv4 6.1.2 Suche nach Adresse](http://www.dwarfstd.org/doc/DWARF4.pdf#%5B%7B%22num%22%3A347%2C] % 22gen% 22% 3A0% 7D% 2C% 7B% 22name% 22% 3A% 22XYZ% 22% 7D% 2C0% 2C792% 2Cnull% 5D) Entsprechende Tabelle zum Abschnitt ".debug_aranges" Ist geschrieben. Leider unterstützt "pyelftools" nicht ".debug_aranges" (https://github.com/eliben/pyelftools/wiki/User's-guide#dwarf). Also werde ich offen nach CU / DIE suchen.
def get_func_info(dwarf_info: DWARFInfo, addr: int) -> Optional[Dict]:
"""
Funktionsinformationen abrufen, einschließlich der durch addr angegebenen Adresse
:param dwarf_info:DWARF-Informationen
:param addr:Programm zähler
:return:Funktionsinformationen wie Funktionsname und Adresse
"""
#Von jeder Zusammenstellungseinheit
for cu in dwarf_info.iter_CUs():
#Während der Wiederholung von DIE
for die in cu.iter_DIEs():
try:
#Auf der Suche nach der Funktion DIE,
if die.tag == 'DW_TAG_subprogram':
#Suchen Sie den von der Funktion belegten Adressbereich
low_pc = die.attributes['DW_AT_low_pc'].value
high_pc_attr = die.attributes['DW_AT_high_pc']
high_pc_attr_class = describe_form_class(high_pc_attr.form)
if high_pc_attr_class == 'address':
high_pc = high_pc_attr.value
elif high_pc_attr_class == 'constant':
high_pc = low_pc + high_pc_attr.value
else:
print('Error: invalid DW_AT_high_pc class:{}\n'.format(high_pc_attr_class))
continue
#Bingo, wenn die angegebene Adresse diesem Funktionsbereich entspricht
if low_pc <= addr < high_pc:
ret = dict()
ret["addr"] = addr
ret["cu"] = cu
ret["func_name"] = die.attributes['DW_AT_name'].value.decode("utf-8")
ret["func_addr"] = low_pc
ret["offset_from_func"] = addr - low_pc
ret.update(get_file_line_from_address(cu, addr))
return ret
except KeyError:
continue
return None
In allen CompileUnits suchen wir nach DIE und iterieren DIE DW_TAG_subprogram
, das die Funktion darstellt. Der von der Funktion belegte Adressbereich ist [2.17 Codeadressen und -bereiche](http://dwarfstd.org/doc/DWARF4.pdf#%5B%7B%22num%22%3A137%2C%22gen%22%3A0% Die Spezifikationen sind in 7D% 2C% 7B% 22name% 22% 3A% 22XYZ% 22% 7D% 2C0% 2C792% 2Cnull% 5D) geschrieben.
Wenn das Attribut [DW_AT_low_pc
, DW_AT_high_pc
), das das Intervall angibt, einen einzelnen Adressbereich angibt oder wenn aufgrund von Optimierung usw. mehrere diskontinuierliche Bereiche vorhanden sind, wenn der Bereich durch DW_AT_ranges
angegeben wird Es gibt.
Lassen Sie uns zunächst nur einen einzigen Adressbereich behandeln. Wenn "DW_AT_ranges" angezeigt wird, implementieren Sie es zu diesem Zeitpunkt.
Von hier aus werden domänenspezifische Wörter wie Attribute und Klassen verstreut, sodass ich sie einmal organisieren werde.
DW_AT_TAG_ * code> unterscheiden.
- -Attribut (
DW_AT _ * code>)
- In DIE enthaltenes Informationselement. Name oder Adressbereich.
- Klasse
Die durch das Attribut
- ausgedrückte Bedeutung.
Adresse code>, Konstante code>, Zeichenfolge code>.
- -Format (
DW_FORM_ * code>)
- Zeigt an, wie es als Entität gehalten wird.
DW_FORM_data2 code> ist ein 2-Byte-Wert, DW_FORM_data4 code> ist ein 4-Byte-Wert.
Wenn Sie diese Begriffe verwenden, um zu beschreiben, wie die Startadresse einer Funktion ermittelt wird,
Wenn Sie für die CU in main.c von der DIE oben folgen, erreichen Sie die DIE der Hauptfunktion. Das Tag für dieses DIE ist "DW_TAG_subprogram" und das Attribut "DW_AT_name" ist "main". Das Attribut
DW_AT_low_pc
hat einen Wert der Klasseaddress
in der FormDW_FORM_addr
. Das ist die Startadresse der Hauptfunktion.
Es wird sein.
Nachdem wir die Startadresse kennen, schauen wir uns die Endseite an, das Attribut DW_AT_high_pc
.
[2.17.2 Kontinuierlicher Adressbereich](http://dwarfstd.org/doc/DWARF4.pdf#%5B%7B%22num%22%3A137%2C%22gen%22%3A0%7D%2C%7B%22name% Gemäß 22% 3A% 22XYZ% 22% 7D% 2C0% 2C792% 2Cnull% 5D) kann das Attribut "DW_AT_high_pc" "Adresse" oder "Konstante" sein.
Wenn es sich um die Klasse "Adresse" handelt, handelt es sich um * die verschobene Adresse *, sodass Sie sich diese als geladene Speicheradresse vorstellen können [^ 4]. Wenn die Klasse "konstant" ist, bedeutet dies die Versatzadresse von "DW_AT_low_pc".
[^ 4]: Ich denke, ASLR ist deaktiviert, wenn gdb funktioniert, daher sollte es dem Preis zum Zeitpunkt der Verknüpfung entsprechen (ich bin nicht sicher, weil ich nicht richtig über gemeinsam genutzte Bibliotheken nachdenke).
Nachdem Sie die Start- und Endadressen dieser Funktion kennen, können Sie feststellen, ob dies die gesuchte Funktion ist.
Was soll ich mit der Quelldatei tun, nachdem ich den Funktionsnamen und die Adresse kenne?
[6.2 Informationen zur Zeilennummer](http://www.dwarfstd.org/doc/DWARF4.pdf#%5B%7B%22num%22%3A350%2C%22gen%22%3A0%7D%2C%7B%22name% Laut 22% 3A% 22XYZ% 22% 7D% 2C0% 2C792% 2Cnull% 5D) können Sie im Abschnitt .debug_line
sehen, dass es eine Tabelle zur Konvertierung von Adresse in Quellcode gibt.
Die Grundidee besteht darin, eine Referenztabelle von der Adresse des Objekts bis zum Namen, der Zeile und der Spaltennummer der Quellcodedatei zu haben. Wenn Sie jedoch einfach eine solche Tabelle erstellen, wird sie zu einer riesigen Tabelle, die um ein Vielfaches größer ist als das unten gezeigte Objekt.
Adresse | Name der Quelldatei | Anzahl der Zeilen | Anzahl an Ziffern |
---|---|---|---|
0x00abcd | main.c | 10 | 8 |
0x00abce | main.c | 11 | 10 |
0x00abcf | main.c | 11 | 10 |
0x00abcg | main.c | 12 | 8 |
0x00abch | main.c | 12 | 8 |
0x00abci | main.c | 13 | 8 |
Daher reduziert DWARF die Speichergröße durch die folgenden zwei Methoden.
Adresse | Name der Quelldatei | Anzahl der Zeilen | Anzahl an Ziffern |
---|---|---|---|
0x00abcd | main.c | 10 | 8 |
0x00abce | main.c | 11 | 10 |
0x00abcg | main.c | 12 | 8 |
0x00abci | main.c | 13 | 8 |
Spart Größe, indem doppelte Zeilen entfernt werden, wenn die Maschinenanweisungen mehrere Bytes umfassen.
Entwerfen Sie Ihre eigene Stapelmaschine ohne Aufzeichnungstabellen und speichern Sie damit die Aufzeichnungsgröße.
_Personen, Leute, Leute, Leute, Leute, Leute, Leute, Leute, Leute, Leute
> Entwerfen Sie eine Stapelmaschine <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄
Es wurde plötzlich eine super Erweiterung, aber zum Glück verschiebt "Pyelftools" die Stapelmaschine und erweitert sie in die Adress- / Zeilennummertabelle, daher bin ich dankbar, sie zu verwenden. Eigentlich class Line Program Die Stack-Maschine läuft hier herum.
Die Funktion zum Abrufen des Quelldateinamens und der Anzahl der Zeilen von der Adresse lautet also wie folgt.
def get_file_line_from_address(cu: CompileUnit, addr: int) -> Dict:
"""
Suchen Sie in der Kompiliereinheit nach Informationen zum Namen der Quellcodedatei und zur Zeilenanzahl
:param cu:Einheiteninformationen zusammenstellen
:param addr:Adresse im Objekt
:return:Name der Quellcodedatei und Anzahl der Zeilen
"""
top_die = cu.get_top_DIE() # type: DIE
#Das Verzeichnis zur Kompilierungszeit.
#Der Quellpfad wird relativ zu diesem Verzeichnis angezeigt
if "DW_AT_comp_dir" in top_die.attributes.keys():
comp_dir = top_die.attributes["DW_AT_comp_dir"].value.decode('utf-8')
else:
comp_dir = ""
line_program = cu.dwarfinfo.line_program_for_CU(cu) # type: LineProgram
for entry in reversed(line_program.get_entries()): # type: LineProgramEntry
if entry.state is not None and not entry.state.end_sequence:
if entry.state.address < addr:
#Der Adressbereich des Eintrags enthielt die gesuchte Adresse
#Suchen Sie den vollständigen Pfad der Datei
fe = line_program["file_entry"][entry.state.file - 1]
name = fe.name.decode('utf-8')
if fe.dir_index != 0:
#Ein anderes Verzeichnis, nicht das Verzeichnis zur Kompilierungszeit(Als relativer Pfad notiert)Wenn es eine Quelle in gibt
d = line_program["include_directory"][fe.dir_index - 1].decode('utf-8')
else:
d = ""
path = posixpath.normpath(posixpath.join(comp_dir, d, name))
ret = dict()
ret["path"] = path
ret["line"] = entry.state.line
return ret
return dict()
Nun, hier ist die Produktion. Um zum Anruf zurückzukehren
Ich brauche diese Informationen.
Die Grundidee ist wie immer, einen riesigen Tisch zu haben. Zum Beispiel
Adresse | Stapelzeiger des Aufrufers | Registrieren Sie r0, um zurückzukehren | Registrieren Sie r1, um wiederhergestellt zu werden | 呼び元Adresse |
---|---|---|---|---|
0x100 | .. | .. | .. | .. |
0x101 | .. | .. | .. | .. |
0x102 | .. | .. | .. | .. |
0x103 | .. | .. | .. | .. |
Durch Erstellen einer so großen Tabelle können die Informationen für die Rückkehr zum Anrufer an einer beliebigen Adresse wiederhergestellt werden, während sich der Verbrauchsstatus des Registerstapels mit fortschreitender Funktion jeden Moment ändert.
Hier ist eine kleine Definition des Begriffs, aber definieren Sie grob, dass der obere Rand des Stapelzeigers bei der Rückkehr zum Funktionsaufrufer CFA heißt. Um genau zu sein: 6.4 Call Frame Information
An area of memory that is allocated on a stack called a “call frame.” The call frame is identified by an address on the stack. We refer to this address as the Canonical Frame Address or CFA. Typically, the CFA is defined to be the value of the stack pointer at the call site in the previous frame (which may be different from its value on entry to the current frame).
Der auf dem Stapel zugewiesene Speicherbereich wird als "Aufrufrahmen" bezeichnet. Der Aufrufrahmen wird als Adresse auf dem Stapel identifiziert. Die Adresse für diese Identifikation heißt CFA (Referenzrahmenadresse). In der Regel ist CFA der Wert des Stapelzeigers unmittelbar vor dem Funktionsaufruf.
Also, * normalerweise * ist ein Punkt, aber es ist ein grobes Verständnis.
Nun, wie üblich komprimiert DWARF diese Tabelle mit einer Stapelmaschine, aber die eigentliche Arbeit wird von "Pyelftools" erledigt. Wenn Sie die Details der Erweiterungsmethode aufschreiben, können Sie das Ende nicht sehen, daher erkläre ich nur die Atmosphäre, in der Sie der erweiterten Tabelle nachjagen.
Zuerst habe ich versucht, die Informationen der Funktion LZ4_compress_fast ()
anzuzeigen, die im Abschnitt .debug_frame
mit eu-readelf -w
aufgezeichnet wurden. Der Teil mit Programm
ist der Code der Stapelmaschine.
[ 70] CIE length=20
CIE_id: 0
version: 3
augmentation: "zR"
code_alignment_factor: 1
data_alignment_factor: -8
return_address_register: 16
Augmentation data: 0x3 (FDE-Adresscodierung udata4)
Program:
def_cfa r7 (rsp) at offset 8
offset r16 (rip) at cfa-8
nop
nop
:
:
[ 470] FDE length=44 cie=[ 70]
CIE_pointer: 1028
initial_location: 0x00000000004046a3 <LZ4_compress_fast>
address_range: 0xa1
Program:
advance_loc4 1 to 0x1
def_cfa_offset 16
offset r6 (rbp) at cfa-16
advance_loc4 3 to 0x4
def_cfa_register r6 (rbp)
advance_loc4 156 to 0xa0
def_cfa r7 (rsp) at offset 8
nop
nop
nop
nop
nop
nop
nop
Sie werden die Adress- / Registerkorrespondenztabelle von hier aus wiederherstellen. Wenn Sie sie jedoch mit pyrlftools dekodieren, wird sie auf das folgende Formular erweitert.
# entries = cu.dwarfinfo.CFI_entries()
# entry.cie.header
Container({'version': 3,
'code_alignment_factor': 1,
'augmentation': b'',
'length': 20,
'data_alignment_factor': -8,
'CIE_id': 4294967295,
'return_address_register': 16})
# entry.decoded()
DecodedCallFrameTable(table=[{'pc': 0x4046A3,
'cfa': CFARule(reg=7, offset=8, expr=None)},
16: RegisterRule(OFFSET, -8),
{'pc': 0x4046A4,
'cfa': CFARule(reg=7, offset=16, expr=None),
6: RegisterRule(OFFSET, -16),
16: RegisterRule(OFFSET, -8)},
{'pc': 0x4046A7,
'cfa': CFARule(reg=6, offset=16, expr=None),
6: RegisterRule(OFFSET, -16)
16: RegisterRule(OFFSET, -8),},
{'pc': 0x404743,
'cfa': CFARule(reg=7, offset=8, expr=None),
6: RegisterRule(OFFSET, -16),
16: RegisterRule(OFFSET, -8)}],
reg_order=[16, 6])
Die folgende Tabelle fasst dies in Form einer Tabelle zusammen.
Übrigens wird hier das Register return_address_register, in dem das Rückgabeziel gespeichert ist, Nr. 16, $ rip zugewiesen, aber je nach Architektur möglicherweise nicht dem tatsächlichen Register zugewiesen.
Adresse | CFA | r6 ($rbp) | r16 ($rip, return_address_register) |
---|---|---|---|
0x4046A3 | r7+8 | - | *(CFA-8) |
0x4046A4 | r7+16 | *(CFA-16) | *(CFA-8) |
0x4046A7 | r6+16 | *(CFA-16) | *(CFA-8) |
0x404743 | r7+8 | *(CFA-16) | *(CFA-8) |
Verwenden wir diese Tabelle tatsächlich, um die Adresse des Aufrufers wiederherzustellen, der LZ4_compress_fast ()
aufgerufen hat.
(gdb) bt
#0 LZ4_compress_generic (acceleration=1, dictIssue=<optimized out>, dict=<optimized out>, tableType=<optimized out>,
outputLimited=<optimized out>, maxOutputSize=<optimized out>, inputSize=57, dest=0x61e010 "",
source=0x41b4b8 "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", cctx=0x7fffffff9530) at lz4.c:578
#1 LZ4_compress_fast_extState (state=0x7fffffff9530, source=0x41b4b8 "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", dest=0x61e010 "",
inputSize=57, maxOutputSize=73, acceleration=1) at lz4.c:739
#2 0x0000000000404722 in LZ4_compress_fast (source=0x41b4b8 "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", dest=0x61e010 "",
inputSize=57, maxOutputSize=73, acceleration=1) at lz4.c:760
#3 0x0000000000404776 in LZ4_compress_default (source=0x41b4b8 "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", dest=0x61e010 "",
inputSize=57, maxOutputSize=73) at lz4.c:771
#4 0x0000000000400957 in main () at simple_buffer.c:54
Wechseln Sie zunächst zu dem Stapelrahmen, in dem die Funktion LZ4_compress_fast ()
ausgeführt wird.
(gdb) frame 2
#2 0x0000000000404722 in LZ4_compress_fast (source=0x41b4b8 "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", dest=0x61e010 "",
inputSize=57, maxOutputSize=73, acceleration=1) at lz4.c:760
760 int const result = LZ4_compress_fast_extState(ctxPtr, source, dest, inputSize, maxOutputSize, acceleration);
Die Ausführungsadresse lautet zu diesem Zeitpunkt 0x404722. Wenn Sie also die obige Tabelle nach Adresse durchsuchen,
Adresse | CFA | r6 ($rbp) | r16 ($rip, return_address) |
---|---|---|---|
0x4046A7 | r6+16 | *(CFA-16) | *(CFA-8) |
Diese Zeile stimmt überein. Der Speicherort des CFA ist "$ rbp + 16", weil "r6 == $ rbp". Und da r16`` CFA-8
ist, lautet ** return_address **:
(gdb) x $rbp+16-8
0x7fffffffd568: 0x00404776
Sollte sein. Da diese Adresse sicherlich mit der Ausführungsadresse von Frame 3 übereinstimmt, bedeutet dies, dass die Anruferadresse wiederhergestellt werden kann.
Hier gibt es einige Einschränkungen. Ich beziehe mich auf r6 als Speicherort des CFA, aber dies ist nicht das r6, das ich für den Rückruf von Anrufen wiederhergestellt habe, aber jetzt ist es r6 zum Zeitpunkt der Ausführung von 0x404722. Wenn nicht, wird es zirkulieren, da es auf CFA basiert, um r6 wiederherzustellen.
Mit anderen Worten:
Es ist notwendig, in der Reihenfolge zu berechnen.
Basierend auf dem oben Gesagten werden die folgenden Funktionen verarbeitet. Neben der Wiederherstellung der return_address und der Wiederherstellung des Registers wird hier auch der Stack zurückgespult.
def get_prev_frame(cu: CompileUnit,
addr: int,
regs: List[int],
read_mem: Callable[[int], int]) -> Tuple[Optional[int], Optional[List[int]]]:
"""
Ausführungsadressregister und Speicher(Hauptsächlich stapeln)Identifizieren Sie den Stapelrahmen.
Stellen Sie die Adresse des Anrufers und das Register zu diesem Zeitpunkt wieder her
:param cu:CU-Informationen
:param addr:Ausführungsadresse
:param regs:Liste der nach DWARF-Registernummer indizierten Register
:param read_mem:Eine Funktion, die Speicher liest. Lesen ist ein Zeiger(registrieren)Größe
:return:Anruferadresse und Registrierung
"""
if cu.dwarfinfo.has_CFI():
entries = cu.dwarfinfo.CFI_entries()
else:
# .debug_Es gibt keinen Rahmen
entries = []
for entry in entries:
if "initial_location" not in entry.header.keys():
continue
start = entry.header.initial_location
end = start + entry.header.address_range
if not (start <= addr < end):
continue
dec = entry.get_decoded()
for row in reversed(dec.table):
if row["pc"] <= addr:
#Stellen Sie die Absenderadresse und ihre Register wieder her
cfa_rule = row["cfa"] # type: CFARule
assert cfa_rule.expr is None, "DWARF-Ausdruck wird nicht unterstützt"
cfa = regs[cfa_rule.reg] + cfa_rule.offset
return_address_rule = row[entry.cie.header.return_address_register] # type: RegisterRule
assert return_address_rule.type == RegisterRule.OFFSET, "Wird nur OFFSET unterstützt"
return_address = cfa + return_address_rule.arg
prev_regs = regs[:]
for key, reg in row.items():
if isinstance(reg, RegisterRule):
assert reg.type == RegisterRule.OFFSET, "Wird nur OFFSET unterstützt"
prev_regs[key] = read_mem(cfa + reg.arg)
#Stapel zurückspulen
unwind_stack(prev_regs, cfa)
return read_mem(return_address), prev_regs
return None, None
Jetzt, da es fertig ist, lassen Sie es uns laufen. Platzieren Sie bt.py
im aktuellen Verzeichnis und versuchen Sie, das Python-Modul von gdb aus aufzurufen. Wenn Sie den Frame weiter verschieben, ändert sich der Registerwert. Vergessen Sie also nicht, ihn zurück zum ersten Frame zu verschieben!
(gdb) frame 0
(gdb) source bt.py
#0 0x00000000004010a4 in LZ4_compress_fast_extState() at /home/oreore/lz4/lib/lz4.c:575
#1 0x0000000000404722 in LZ4_compress_fast() at /home/oreore/lz4/lib/lz4.c:760
#2 0x0000000000404776 in LZ4_compress_default() at /home/oreore/lz4/lib/lz4.c:771
#3 0x0000000000400957 in main() at /home/oreore/lz4/examples/simple_buffer.c:54
Es sieht ziemlich gut aus, aber wenn Sie genau hinschauen, werden Sie feststellen, dass ein Frame fehlt. Dies liegt daran, dass wir die Inline-Erweiterung nicht richtig analysiert haben. Die Funktion "LZ4_compress_generic ()" wird in der Funktion "LZ4_compress_fast_extState ()" inline erweitert, aber ich decodiere sie als "LZ4_compress_fast_extState ()", ohne es zu bemerken.
Um Inline zu interpretieren, müssen Sie DW_TAG_inlined_subroutine
ernst nehmen, aber es gibt nicht genug Platz, um es zu schreiben, also werde ich hier aufhören.
Außerdem werden die Parameter nicht angezeigt. Auch dies ist eine Menge Arbeit, zum Beispiel das Betrachten des Abschnitts .debug_loc
, um es zu schreiben (ry)
Kemono Freunde, ich wollte die Fortsetzung sehen. .. ..
Recommended Posts