TL;DR
Extension python Gdb et module pyelftools
Je l'ai utilisé pour [implémenter] le backtrace (https://gist.github.com/sumomoneko/d8415b14f8eddf74a3eb9bd6d521fab3).
Cela peut être utile dans les environnements où la commande gdb backtrace
ne fonctionne pas telle quelle.
Lors du développement chez Kumiko Michiho,
backtrace
.N'est-ce pas la situation qui est rarement courante? Quand j'ai eu des ennuis parce que j'étais jeté dans la savane avec juste ces séries et LED, grâce à Comment parcourir les informations de débogage, je suis devenu un ami qui sait lire Maintenant que j'y suis habitué, j'aimerais revenir en arrière.
Par souci de simplicité du retest, je l'ai confirmé dans l'environnement de x64 ubuntu 16.04 gcc5.4 / python3.5 au lieu de l'environnement embarqué [^ 1].
[^ 1]: En tant que module intégré, nous l'avons mis dans une véritable bataille avec RX.
Les bases de DWARF sont expliquées en détail dans Walking Debug Information, veuillez donc vous y référer. Aussi, [ici](https://ja.osdn.net/projects/drdeamon64/wiki/DWARF%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3% L'article 83% 95% E3% 82% A9% E3% 83% BC% E3% 83% 9E% E3% 83% 83% E3% 83% 88) était également très utile.
De plus, je n'entrerai pas dans les détails sur gdb Python-API. Je ne fais pas de choses compliquées en utilisant le module python gdb
, donc je pense que vous pouvez le comprendre en regardant l'exemple.
pyelftools
Il est difficile de décoder DWARF à mains nues, alors installez le module python pyelftools qui décode une partie considérable. Je veux isoler l'environnement avec virtualenv etc., mais quand j'appelle python via gdb, il ne voit pas le chemin du module dans virtualenv, donc je le mets directement dans le système.
$ sudo pip3 install pyelftools
Sinon, développez le module quelque part et placez-le au début du script python
import sys
sys.path.append("/home/oreore/virtualenv/python/site-packages")
Vous pouvez entrer le chemin comme [^ 2].
[^ 2]: Veuillez indiquer s'il existe une solution brillante qui peut convaincre gdb.
Utilisez un code abordable pour voir comment cela fonctionne. Ici, nous utiliserons la bibliothèque de compression / décompression Mach lz4 et son application de test simpleBuffer
. Par exemple, ici explique ce qu'est «lz4», alors veuillez vous y référer.
Maintenant, clonez et construisez comme suit.
$ git clone https://github.com/lz4/lz4.git
$ cd lz4
$ CFLAGS="-g3 -fno-dwarf2-cfi-asm" make all
En complément de l'indicateur de construction, dans gcc (Ubuntu 5.4.1-2ubuntu1 ~ 16.04) 5.4.1 20160904 j'ai essayé ici, directive gas CFI /as/CFI-directives.html) semble omettre la section .debug_frame
où les informations de cadre de pile sont stockées, et seule sa version simplifiée (?) .Eh_frame
est sortie.
pyelftools
prend également en charge le décodage .eh_frame
, mais son utilisation prend un certain temps. Donc, cette fois, j'ai demandé au compilateur de sortir .debug_frame
option -fno-dwarf2-cfi-asm
. Je l'ai demandé avec .html). Cela peut avoir changé avec ubuntu / gcc récent.
Tout d'abord, exécutez-le avec gdb, coupez à une partie appropriée et effectuez un retour arrière.
$ 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
Reproduisons ceci.
J'ai préparé celui qui est terminé dans gist, donc je voudrais l'expliquer en conséquence.
J'aimerais commencer tout de suite à analyser DWARF, mais avant cela, organisons l'environnement d'exécution.
Cette fois, nous allons créer un script python qui s'exécute sur gdb x64, mais il n'est pas indispensable de l'exécuter sur gdb séparément. De plus, en raison de la nature de DWARF, cela ne signifie pas qu'il doit être x64. Si les fonctions suivantes sont préparées, il peut être exécuté en tant que script python unique indépendamment de gdb et de l'architecture.
Commençons par séparer ces fonctions en fonctions afin de pouvoir facilement modifier la cible ultérieurement. La mise en œuvre de chaque fonction est expliquée ci-dessous.
Obtenez le chemin du fichier exécutable qui n'est pas supprimé. Nous récupérons le nom de l'objet fonctionnant sous gdb (appelé "inférieur" dans gdb). S'il est intégré, ce sera un fichier binaire de pré-décapage qui a été enregistré avant la gravure sur ROM.
def get_elf_file_name() -> str:
"""
Obtenez le nom du fichier contenant les informations de débogage.
Dans l'environnement gdb, gdb.objfiles()[0].Trouvé dans le nom du fichier
:return:Chemin de fichier avec informations de débogage
"""
import gdb
return gdb.objfiles()[0].filename
uintptr_t ret = *reinterpret_cast<uintptr_t*>(addr);
Voici une image de cette opération avec gdb-python. Il est utilisé pour récupérer la mémoire pointée par le registre. Comme la zone de pile est requise comme zone de mémoire, il est bon de définir au moins la zone de pile comme cible de vidage lors de la création de votre propre vidage.
def read_uintptr_t(addr: int) -> int:
"""
uintptr_Lire les données de taille t de la mémoire
uintptr_t ret = *reinterpret_cast<uintptr_t*>(addr);
:param addr:adresse
:return:Les données
"""
import gdb
uintptr_t = gdb.lookup_type('uintptr_t')
return int(gdb.Value(addr).reinterpret_cast(uintptr_t.pointer()).dereference())
Je viens d'écrire (gdb) p $ pc
dans python-gdb.
Si vous effectuez votre propre vidage, est-ce comme enregistrer l'adresse source qui est passée au traitement des exceptions?
def get_pc() -> int:
"""
Obtenez le compteur de programmes
:return:Valeur PC
"""
import gdb
return int(gdb.parse_and_eval("$pc"))
DWARF est conçu pour prendre en charge une variété d'architectures. Par conséquent, les registres ne sont pas traités avec des noms spécifiques à l'architecture, mais sont numérotés et gérés. Alors, quel registre est quel numéro? Rapidement [code source de gdb](https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=blob;f=gdb Jetons un coup d'œil à /amd64-tdep.c;h=b589d93940f1f498177ba91273190dc9b0714370;hb=HEAD#l156).
Voici donc le produit fini. J'ai commenté les registres stN
et mmN
, mais c'est un jeu parce que les registres larges étaient un problème [^ 3].
Si vous recherchez un numéro de registre pour une architecture différente, vous devriez rechercher un fichier similaire avec un nom de processeur dans un emplacement similaire dans l'arborescence des sources gdb.
[^ 3]: À l'origine, je l'ai écrit pour déboguer le processeur intégré, donc je n'ai pas un tel registre.
def get_registers() -> List[int]:
"""
Collecter les valeurs de registre de gdb en fonction du numéro de registre DWARF
https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=blob;f=gdb/amd64-tdep.c;h=b589d93940f1f498177ba91273190dc9b0714370;hb=HEAD#l156
:return:Liste des valeurs de registre par ordre de numéro de registre DWARF
"""
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
Lorsque vous revenez dans l'arborescence des appels, vous devez rembobiner la pile. Puisque le registre qui contient la pile dépend de l'architecture, il est séparé en fonctions comme traitement de rembobinage.
def unwind_stack(regs: List[int], cfa: int) -> None:
"""
Définissez l'adresse dans le registre qui contient le pointeur de pile.
Pour x64$Pour rsp. Le numéro de registre DWARF est 7, alors définissez l'adresse ici.
:param regs:Tableau de registres(Ordre des numéros de registre DWARF)
:param cfa:Adresse à définir comme pointeur de pile
:return: None
"""
regs[7] = cfa
C'est tout pour la dépendance de gdb et d'architecture.
Le flux global du traitement des traces est le suivant.
Ceci est écrit solidement avec main ()
.
def main() -> None:
"""
Afficher la trace
"""
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()
#Pour la première fois à partir des informations de position d'arrêt actuelle
pc = get_pc()
regs = get_registers()
i = 0
while True:
#Obtenir des informations sur la fonction de la position d'arrêt
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
#Regardez le cadre de la pile et suivez l'appelant
pc, regs = get_prev_frame(fi["cu"], pc, regs, read_uintptr_t)
if pc is None:
break
"Obtenir l'adresse d'arrêt, registre (& pointeur de pile)" n'appelle que la fonction expliquée dans la section précédente, nous allons donc l'examiner dans l'ordre à partir de "Obtenir les informations de la fonction, y compris l'adresse".
En tant que route de capture DWARF pour obtenir une fonction qui l'inclut à partir d'une certaine adresse,
--Lick all CU / DIE et trouver une plage d'adresses de hit
Il y a deux manières. Cette dernière est exactement la méthode à cet effet, [DWARFv4 6.1.2 Recherche par 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) Tableau correspondant à la section .debug_aranges
Est écrit.
Malheureusement, pyelftools
ne prend pas en charge .debug_aranges
(https://github.com/eliben/pyelftools/wiki/User's-guide#dwarf). Donc, je vais franchement chercher CU / DIE.
def get_func_info(dwarf_info: DWARFInfo, addr: int) -> Optional[Dict]:
"""
Obtenir des informations sur la fonction, y compris l'adresse indiquée par addr
:param dwarf_info:Informations DWARF
:param addr:Compteur de programme
:return:Informations de fonction telles que le nom et l'adresse de la fonction
"""
#De chaque unité de compilation
for cu in dwarf_info.iter_CUs():
#Lors de l'itération de DIE
for die in cu.iter_DIEs():
try:
#À la recherche de la fonction DIE,
if die.tag == 'DW_TAG_subprogram':
#Trouvez la plage d'adresses occupée par la fonction
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 si l'adresse spécifiée correspond à cette plage de fonctions
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
À partir de toutes les CompileUnits, nous rechercherons DIE et itérerons DIE DW_TAG_subprogram
qui représente la fonction. La plage d'adresses occupée par la fonction est [2.17 Adresses et plages de code](http://dwarfstd.org/doc/DWARF4.pdf#%5B%7B%22num%22%3A137%2C%22gen%22%3A0% Les spécifications sont écrites en 7D% 2C% 7B% 22name% 22% 3A% 22XYZ% 22% 7D% 2C0% 2C792% 2Cnull% 5D).
Si l'attribut [DW_AT_low_pc
, DW_AT_high_pc
) indiquant l'intervalle indique une seule plage d'adresses, ou s'il existe plusieurs plages discontinues en raison de l'optimisation, etc., si la plage est spécifiée par DW_AT_ranges
Il y a.
Pour l'instant, ne traitons qu'une seule plage d'adresses. Si DW_AT_ranges
apparaît, implémentez-le à ce moment-là.
À partir de là, les mots spécifiques au domaine, tels que les attributs et les classes, seront dispersés, donc je les organiserai une fois.
DW_AT_TAG_ * code>.
Attribut - (
DW_AT _ * code>)
- Élément d'information contenu dans DIE. Nom ou plage d'adresses.
- classe
La signification exprimée par l'attribut
- .
adresse code>, constante code>, chaîne code>.
- format (
DW_FORM_ * code>)
- Montre comment il est tenu en tant qu'entité.
DW_FORM_data2 code> est une valeur sur 2 octets, DW_FORM_data4 code> est une valeur sur 4 octets.
Si vous utilisez ces termes pour décrire comment trouver l'adresse de début d'une fonction,
Pour la CU dans main.c, si vous suivez du DIE en haut, vous atteindrez le DIE de la fonction principale. La balise de ce DIE est
DW_TAG_subprogram
, et l'attributDW_AT_name
est" main ". L'attributDW_AT_low_pc
a une valeur de la classe ʻaddresssous la forme
DW_FORM_addr`. C'est l'adresse de début de la fonction principale.
Ce sera.
Maintenant que nous connaissons l'adresse de début, regardons le côté de fin, l'attribut DW_AT_high_pc
.
[2.17.2 Plage d'adresses contiguës](http://dwarfstd.org/doc/DWARF4.pdf#%5B%7B%22num%22%3A137%2C%22gen%22%3A0%7D%2C%7B%22name% Selon 22% 3A% 22XYZ% 22% 7D% 2C0% 2C792% 2Cnull% 5D), l'attribut DW_AT_high_pc
peut être de classe ʻaddress ou
constant. Si c'est la classe ʻaddress
, c'est * l'adresse relocalisée *, donc vous pouvez la considérer comme l'adresse mémoire chargée [^ 4]. Si la classe est «constante», cela signifie l'adresse de décalage de «DW_AT_low_pc».
[^ 4]: Je pense que l'ASLR est désactivé lorsque gdb fonctionne, il devrait donc être le même que le prix demandé au moment de la liaison (je ne suis pas sûr car je ne pense pas correctement aux bibliothèques partagées)
Maintenant que vous connaissez les adresses de début et de fin de cette fonction, vous pouvez déterminer s'il s'agit de la fonction que vous recherchez.
Maintenant que je connais le nom et l'adresse de la fonction, que dois-je faire avec le fichier source?
[6.2 Informations sur le numéro de ligne](http://www.dwarfstd.org/doc/DWARF4.pdf#%5B%7B%22num%22%3A350%2C%22gen%22%3A0%7D%2C%7B%22name% Selon 22% 3A% 22XYZ% 22% 7D% 2C0% 2C792% 2Cnull% 5D), vous pouvez voir qu'il y a une table de conversion adresse-code source dans la section .debug_line
.
L'idée de base est d'avoir une table de référence depuis l'adresse de l'objet jusqu'au nom du fichier de code source, la ligne et le numéro de colonne. Cependant, si vous créez simplement une telle table, elle deviendra une énorme table plusieurs fois plus grande que l'objet comme indiqué ci-dessous.
adresse | Nom du fichier source | Nombre de lignes | Nombre de chiffres |
---|---|---|---|
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 |
Par conséquent, DWARF réduit la taille de stockage par les deux méthodes suivantes.
adresse | Nom du fichier source | Nombre de lignes | Nombre de chiffres |
---|---|---|---|
0x00abcd | main.c | 10 | 8 |
0x00abce | main.c | 11 | 10 |
0x00abcg | main.c | 12 | 8 |
0x00abci | main.c | 13 | 8 |
Économise de la taille en supprimant les lignes en double lorsque les instructions de la machine comportent plusieurs octets.
Concevez votre propre machine d'empilage sans enregistrer la table et utilisez-la pour économiser la taille d'enregistrement.
_Personnes, personnes, personnes, personnes, personnes, personnes, personnes, personnes, personnes, personnes
> Concevoir une machine à empiler <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄
Cela est soudainement devenu une super extension, mais heureusement, pyelftools déplace la machine de pile et l'étend vers la table des numéros d'adresses / lignes, je suis donc reconnaissant de l'utiliser. En fait, programme de ligne de classe La machine de pile tourne ici.
Ainsi, la fonction pour obtenir le nom du fichier source et le nombre de lignes de l'adresse est la suivante.
def get_file_line_from_address(cu: CompileUnit, addr: int) -> Dict:
"""
Rechercher le nom du fichier de code source et les informations sur le nombre de lignes à partir de l'unité de compilation
:param cu:compiler les informations sur les unités
:param addr:Adresse dans l'objet
:return:Nom du fichier de code source et nombre de lignes
"""
top_die = cu.get_top_DIE() # type: DIE
#Le répertoire au moment de la compilation.
#Le chemin source est affiché par rapport à ce répertoire
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:
#La plage d'adresses de l'entrée contenait l'adresse que vous recherchiez
#Trouvez le chemin complet du fichier
fe = line_program["file_entry"][entry.state.file - 1]
name = fe.name.decode('utf-8')
if fe.dir_index != 0:
#Un répertoire différent, pas le répertoire de compilation(Noté comme chemin relatif)S'il y a une source dans
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()
Eh bien, voici la production. Pour revenir à l'appel
J'ai besoin de ces informations.
L'idée de base est, comme d'habitude, d'avoir une grande table. Par exemple
adresse | Pointeur de pile de l'appelant | Enregistrez r0 pour revenir | Enregistrez R1 à restaurer | 呼び元adresse |
---|---|---|---|---|
0x100 | .. | .. | .. | .. |
0x101 | .. | .. | .. | .. |
0x102 | .. | .. | .. | .. |
0x103 | .. | .. | .. | .. |
En créant une table aussi volumineuse, les informations de retour à l'appelant peuvent être restaurées à n'importe quelle adresse tandis que l'état de consommation de la pile de registres change à chaque instant au fur et à mesure que la fonction progresse.
Voici une petite définition du terme, mais définissons grossièrement que le haut du pointeur de pile lors du retour à l'appelant de la fonction est appelé CFA. Pour être exact: 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).
La zone mémoire allouée sur la pile est appelée "call frame". La trame d'appel est identifiée comme une adresse sur la pile. L'adresse pour cette identification est appelée CFA (adresse de trame de référence). En règle générale, CFA est la valeur du pointeur de pile juste avant l'appel de fonction.
Donc, * généralement * est un point, mais c'est une compréhension approximative.
Eh bien, comme d'habitude, DWARF compresse cette table en utilisant une machine à pile, mais le travail réel est effectué par pyelftools
. Si vous notez les détails de la méthode d'expansion, vous ne pourrez pas voir la fin, donc je n'expliquerai que l'atmosphère de la poursuite du tableau développé.
Tout d'abord, j'ai essayé d'afficher les informations de la fonction LZ4_compress_fast ()
enregistrées dans la section .debug_frame
avec ʻeu-readelf -w`. La partie avec «Programme» est le code de la machine d'empilage.
[ 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 (Codage d'adresse FDE 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
Vous allez restaurer la table de correspondance adresse / registre à partir d'ici, mais si vous la décodez avec pyrlftools, elle sera développée sous la forme suivante.
# 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])
Le tableau ci-dessous résume cela sous la forme d'un tableau.
A propos, ici, le registre return_address_register qui stocke la destination de retour est affecté au n ° 16 et $ rip, mais il peut ne pas être affecté au registre réel en fonction de l'architecture.
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) |
Utilisons en fait cette table pour restaurer l'adresse de l'appelant qui a appelé LZ4_compress_fast ()
.
(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
Commencez par aller dans le frame de pile où la fonction LZ4_compress_fast ()
est en cours d'exécution.
(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);
L'adresse d'exécution à ce moment est 0x404722, donc si vous recherchez le tableau ci-dessus par adresse,
adresse | CFA | r6 ($rbp) | r16 ($rip, return_address) |
---|---|---|---|
0x4046A7 | r6+16 | *(CFA-16) | *(CFA-8) |
Cette ligne correspond. Puisque r6 == $ rbp
, l'emplacement du CFA est $ rbp + 16
. Et puisque r16
est CFA-8
, ** return_address ** est:
(gdb) x $rbp+16-8
0x7fffffffd568: 0x00404776
Devrait être. Puisque cette adresse correspond certainement à l'adresse d'exécution de la trame # 3, cela signifie que l'adresse de l'appelant peut être restaurée.
Il y a quelques mises en garde ici. Je fais référence à r6 comme étant l'emplacement du CFA, qui est maintenant r6 au moment de l'exécution de 0x404722, et non le r6 restauré pour le rappel d'appel. Sinon, il circulera car il est basé sur CFA pour restaurer r6.
En d'autres termes:
Il est nécessaire de calculer dans l'ordre.
Sur la base de ce qui précède, les fonctions suivantes sont en cours de traitement. En plus de restaurer l'adresse return_address et de restaurer le registre, la pile est également rembobinée ici.
def get_prev_frame(cu: CompileUnit,
addr: int,
regs: List[int],
read_mem: Callable[[int], int]) -> Tuple[Optional[int], Optional[List[int]]]:
"""
Registre et mémoire des adresses d'exécution(Principalement pile)À partir de, identifiez le cadre de la pile,
Restaurer l'adresse de l'appelant et le registre à ce moment
:param cu:Informations CU
:param addr:Adresse d'exécution
:param regs:Liste des registres indexés par numéro de registre DWARF
:param read_mem:Une fonction qui lit la mémoire. Lire est un pointeur(S'inscrire)Taille
:return:Adresse de l'appelant et inscription
"""
if cu.dwarfinfo.has_CFI():
entries = cu.dwarfinfo.CFI_entries()
else:
# .debug_Il n'y a pas de cadre
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:
#Restaurer l'adresse de retour et ses registres
cfa_rule = row["cfa"] # type: CFARule
assert cfa_rule.expr is None, "Expression DWARF non prise en charge"
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, "Non pris en charge sauf OFFSET"
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, "Non pris en charge sauf OFFSET"
prev_regs[key] = read_mem(cfa + reg.arg)
#Rebobinage de la pile
unwind_stack(prev_regs, cfa)
return read_mem(return_address), prev_regs
return None, None
Maintenant qu'il est terminé, exécutons-le. Placez bt.py
dans le répertoire courant et essayez d'appeler le module python depuis gdb. Si vous continuez à décaler l'image, la valeur du registre changera, alors n'oubliez pas de la ramener à la première image!
(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
Cela a l'air plutôt bien, mais si vous regardez de près, vous remarquerez qu'il manque un cadre.
C'est parce que nous n'avons pas analysé correctement l'expansion en ligne. La fonction LZ4_compress_generic ()
est développée en ligne dans la fonction LZ4_compress_fast_extState ()
, mais je la décode comme la fonction LZ4_compress_fast_extState ()
sans m'en apercevoir.
Afin d'interpréter en ligne, vous devez prendre DW_TAG_inlined_subroutine
au sérieux, mais il n'y a pas assez d'espace pour l'écrire, donc je vais m'arrêter ici.
De plus, les paramètres ne sont pas affichés. Encore une fois, c'est beaucoup de travail à faire, comme regarder la section .debug_loc
, pour l'écrire (ry)
Amis Kemono, je voulais voir la suite. .. ..
Recommended Posts