Phase 13: Tools & Protocols

Récupération de données et de types: Structures, Arrays, Objets C++ et DWARF

Un décompilateur imprime a->balancelorsqu' il dispose d' une table de type et *(int *)(param_1 + 4)Cette leçon construit la table à partir des accès, lit la table réelle du DWARF 5 et montre où les deux sont d'accord.

Type: Build

Languages: Python, C, C++, x86-64 assembly

Prerequisites: Phase 13 · 33 (disassembly and control flow), Phase 13 · 34 (lifting to pseudocode)

Time: ~140 minutes

Objectifs d'apprentissage

  • Lisez un accès de membre structure en tant que registre de base plus déplacement, et un accès de tableau en tant que étape de base plus temps d'index, à partir des règles d'adressage Intel SDM.
  • Appliquer manuellement les règles d' alignement du système V à struct accountet expliquer chaque octet de remplissage.
  • Construisez un passe de récupération de type qui transforme les accès dans une liste en compensations de champs, largesses, types, étapes et tailles, avec la preuve pour chacun.
  • Écrivez un lecteur DWARF 5 pour .debug_info- Je suis là ..debug_abbrev- Je suis là ..debug_str_offsets, et .debug_addrd'un objet relocalisable et de le vérifier contre llvm-dwarfdump- Je suis désolé .
  • Comparez la mise en page récupérée avec la mise en page DWARF et nommez chaque différence comme étant une preuve manquante, un accès fusionné ou un type non prouvable.
  • Lisez un vtable Itanium C++, son typeinfo et ses noms déformés à partir de délocalisations, et résolvez un appel virtuel à un slot.

Le problème

Vous avez compiléaccount.csans-get a demandé à un agent ce que bumpL'agent a envoyé l'objet à Ghidra 12.1.4 et a récupéré ceci:

c
void bump(long param_1,int param_2)
{
  *(int *)(param_1 + 4) = param_2 + *(int *)(param_1 + 4);
  return;
}

La même source compilée avec -gdonne ceci du même décompilateur:

c
int bump(account *a,int by)
{
  a->balance = by + a->balance;
  return a->balance;
}

Les instructions sont identiques dans les deux objets. La différence est une table de type. Avec la table, le décompilateur sait que le premier paramètre pointe vers une structure de 32 octets dont le champ à l'offset 4 est un 4 octets intnommé balanceSans elle , le décompilateur sait seulement que quelque chose de 8 octets de large est arrivé .%rdiet que 4 octets à +4ont été lues et écrites.

Tout ce qui est dans cette table a une source.addl 0x4(%rax), %ecxLa largeur 4 vient de la .lLe nom vient d'uneDW_TAG_memberentrée dans .debug_infoCette leçon comporte deux parties: un pass qui récupère ce que les instructions prouvent et un lecteur qui décode ce que le compilateur a enregistré.

Un accès à un membre est un déplacement

L'adresse effective est définie à l'article 3.7.5 du volume 1 du MDD Intel comme Offset = Base + (Index * Scale) + Displacement, avec une échelle de 1, 2, 4 ou 8 et un déplacement de 8, 16 ou 32 bits.RIP + Displacement. Un accès à un membre de structure utilise le premier formulaire sans index: le registre de base contient l'adresse de l'objet et le déplacement est le décalage de membre.

Prenez .bumpà-O0- La liste de llvm-objdump -d -rIl est écrit:

output
       4: 48 89 7d f8                  	movq	%rdi, -0x8(%rbp)
       e: 48 8b 45 f8                  	movq	-0x8(%rbp), %rax
      12: 03 48 04                     	addl	0x4(%rax), %ecx
      15: 89 48 04                     	movl	%ecx, 0x4(%rax)

Décodez03 48 04avec les règles de la leçon 33. 03est ADD r32, r/m32- ModRM 48est 01 001 000Le numéro de la liste:mod = 01désigne un opérand de mémoire à déplacement de 8 bits, reg = 001est %ecx- Je suis là .r/m = 000est %raxLe byte de déplacement est 04L' instruction est de 4 octets à %rax + 4et les ajoute à %ecx- Le .lle suffixe qui llvm-objdumpLes empreintes sont de la taille de l'opéra, donc le champ est de 4 octets de large.%raxest venu de la fente de cadre à -0x8(%rbp), et cette fente a été reçue .%rdi, le premier argument entier de la convention d'appel du système V. Trois faits suivent: le premier paramètre est un pointeur, l'objet qu'il pointe a un champ de 4 bytes à l'offset 4, et bumpIl lit et écrit ce champ.

Rien dans ces octets ne ditint- Je suis là .balanceou struct accountLes 28 octets restants de la structure sont invisibles.bump- Je suis désolé .

Les règles de mise en page du système V

Le x86-64 psABI, section 3.1.2, donne trois règles pour les agrégats. " Les structures et les syndicats prennent l'alignement de leur composant le plus strictement aligné. Chaque membre est assigné à l'offset le plus bas disponible avec l'alignement approprié. La taille d'un objet est toujours un multiple de l'alignement de l'objet. " La figure 3.1 du même document donne les tailles et alignements scalaires: char1 et 1, short2 et 2, int4 et 4, double8 et 8, n'importe quel pointer 8 et 8.

Appliquer les règles à la structure enaccount.c- Le numéro de la liste:

c
struct account {
    char kind;
    int balance;
    short code;
    double rate;
    const char *label;
};
MemberSizeAlignmentLowest offset that fitsPadding before
kind1100
balance4443 (bytes 1 to 3)
code2280
rate88166 (bytes 10 to 15)
label88240

Le dernier membre se termine à 32. L'alignement le plus strict est 8, et 32 est un multiple de 8, de sorte que la taille est 32 sans rembourrage de queue. Neuf octets des 32 sont rembourrés, et le psABI dit "Le contenu de tout rembourrage est indéfini".

L'ordre des membres détermine la taille.double, pointer, int, short, charLes remplissages compensent 0, 8, 16, 20, 22 et se terminent à 23, ce qui fait 24 rondes. sysv_layout()dans code/main.pyIl est possible de trouver des solutions qui permettent de mettre en œuvre les trois règles et de produire les deux résultats.ctypesLe module applique les mêmes règles par l'intermédiaire de la convention du compilateur hôte, et ctypes.sizeof- Je suis là .ctypes.alignment, et le .offsetde chaque champ donne 32, 8 et 0, 4, 8, 16, 24Le programme lui-même imprime la troisième copie lorsque vous la construisez et l'exécutez sur cette machine:

output
sizeof 32 align 8
kind 0 balance 4 code 8 rate 16 label 24
sum 315 tag -3 projected 318.75 label_length 7

Cette construction native est arm64. L'Arm AAPCS64 énonce les mêmes règles agrégées dans sa section 5.10.1, et le document arm64 d'Apple conserve ces cinq tailles scalaires, de sorte que les chiffres concordent entre les deux cibles.-target x86_64-unknown-linux-gnupour que le lecteur ELF de la leçon 33 et le lecteur DWARF ci-dessous travaillent sur eux.

Lisez le reste de la structure à la main

bumpLes quatre autres fonctions sont en account.cLes adresses ci-dessous sont de account-x86_64-O0-g.objdump.txt- Je suis désolé .

sum_balancesindice une gamme de structures:

output
      51: 48 8b 45 f8                  	movq	-0x8(%rbp), %rax
      55: 48 63 4d ec                  	movslq	-0x14(%rbp), %rcx
      59: 48 c1 e1 05                  	shlq	$0x5, %rcx
      5d: 48 01 c8                     	addq	%rcx, %rax
      60: 8b 40 04                     	movl	0x4(%rax), %eax

%raxest list- Je suis là .%rcxest id'une portée de 64 bits, et shlq $0x5multiplié par 32.addq- Je suis là .%raxest list + 32 * i, et la charge est de 4 octets à l' offset 4 de l' élément i. Le nombre de déplacements est le seul endroit dans cet objet où la taille de la structure apparaît comme un nombre.0x49à la jmp 0x49à0x72- Je suis désolé .

tagIl y a deux champs étroits:

output
      8c: 0f be 00                     	movsbl	(%rax), %eax
      98: 0f bf 48 08                  	movswl	0x8(%rax), %ecx

movsblcharge un octet à l'offset 0 et le sign-extension, de sorte que le champ à 0 est un entier de 1 octet signé. movswlcharge deux octets à l'offset 8 avec extension de signe, donc le champ à 8 est un entier signé de 2 octets.-O1Le premier accès devient cmpb $0x6c, (%rdi), qui dit toujours 1 byte à l'offset 0 et ne dit rien sur le signe.

projectedIl y a un flot:

output
      cc: f2 0f 2a 40 04               	cvtsi2sdl	0x4(%rax), %xmm0
      d5: f2 0f 59 40 10               	mulsd	0x10(%rax), %xmm0

cvtsi2sdlconvertisse un nombre entier signé de 32 bits de la mémoire en un double, ce qui confirme que l'offset 4 contient un nombre entier signé de 4 bits. mulsdmultiplié par le double de la mémoire, donc le compensateur 16 a une valeur de point flottant de 8 bytes.

label_lengthlis un pointeur et le suit ensuite:

output
      f3: 48 8b 40 18                  	movq	0x18(%rax), %rax
      f7: 48 63 4d f4                  	movslq	-0xc(%rbp), %rcx
      fb: 80 3c 08 00                  	cmpb	$0x0, (%rax,%rcx)

Le movqIl est 8 octets à l'offset 24.cmpbLa valeur utilisée comme adresse est un pointeur.cmpblisent 1 octet à l' index %rcxde l'intérieur, et à-O1le pointeur est avancé avec leaq 0x1(%rcx), %rcxdans la boucle, donc les éléments pointés vers sont à 1 octet de l'autre.

Si on met les cinq fonctions ensemble, le structure est { i8 at 0, i32 at 4, i16 at 8, f64 at 16, pointer at 24 }Les écarts de 1 à 3 et de 10 à 15 sont exactement le rembourrage prévu par les règles de psABI.const char *, le type déclaré du octet signé à 0, qui est orthographié par la source char, et que les cinq fonctions reçoivent le même type.main, qui passe un objet de cadre à tous les cinq.-O0les sites d'appels rendent visible: leaq -0x70(%rbp), %rdisuivie de addq $0x20, %rdiavant callq bump, et addq $0x40, %rdiavant callq tagLes compensations sont 0, 0x20, et 0x40sont trois éléments d'un même tableau, et le memcpyde 0x60les octets qui le remplissent sont 96 octets, ce qui est trois éléments de 32.

Le passe

trace_function()dans code/main.pyIl est utilisé pour traverser les instructions d'une fonction dans l'ordre d'adresse et conserve un petit état abstrait.-0x8(%rbp)Chaque détenteur a une des trois valeurs.Pointerest une racine plus un décalage constant plus un ensemble de pas, où un pas enregistre qu'un multiple inconnu de ces nombreux octets a été ajouté.Indexest un nombre entier d'une valeur inconnue qui est un multiple d'une échelle.

L' État commence par la tenue des six registres de l' argumentation du système V Pointer("param0", 0)à travers Pointer("param5", 0)Alors chaque instruction s' applique à une règle:

  • Une .movqUne écriture à 32 bits ou plus étroite dans un registre en fait une copie.Indexavec l'échelle 1.
  • Une .leaqavec une base de pointeur produit un pointeur avec le déplacement ajouté, et avec un registre d'index il ajoute scale * index_scale- Je suis en train de faire des progrès.leaqdont la base est %rbpou %rspnomme un objet de cadre, qui est comment mainpasse rows- Je suis désolé .
  • shlq $nsur uneIndexmultiplié par 2^n, et imulq $kmultiplié par k- Je suis désolé .
  • addqd'un pointeur et d'un index conserve le pointeur et ajoute l'échelle de l'indice aux pas. addq $kà un pointeur, le décalage est déplacé et est également enregistré comme un élément de ce registre.
  • Un appel rend chaque registre enregistré par l'appelant inconnu, et les registres d'arguments contenant des pointeurs sont enregistrés comme un site d'appel.
  • Tout opérand de mémoire dont le registre de base contient un pointeur devient un Accessavec la racine et le décalage du pointeur, la largeur et la sorte du mnemonique, et un drapeau de magasin.

La largeur et la gentillesse viennent du seul mnemonique.access_width()carte de la b- Je suis là .w- Je suis là .l, et qles suffixes pour 1, 2, 4 et 8 octets, movsblet movswlà des charges étroites signées, movzblà l'absence de signature, le sdet ssinstructions de la SSE scalaire pour les flottes de 8 octets et de 4 octets, cvtsi2sdlà une source entière de 4 octets signée, et à la source de 16 octets movapsfamille à un vecteur. leaqet le nopLes formulaires ne sont pas des accessoires.

python
if memory.base in FRAME_REGISTERS:
    return Pointer(f"frame({memory.base}{format_signed(memory.displacement) if memory.displacement else ''})", 0)
base = env.get(memory.base)
if not isinstance(base, Pointer):
    return Unknown
strides = set(base.strides)
if memory.index is not None:
    index_value = env.get(memory.index)
    strides.add((index_value.scale if isinstance(index_value, Index) else 1) * memory.scale)
return Pointer(base.root, base.offset + memory.displacement, frozenset(strides), base.origin)

Une charge de 8 octets à travers un pointeur produit un nouveau pointeur dont les noms racinaires du champ d'où il vient, tels que param0->0x18. Lorsque cette valeur est utilisée plus tard comme base, ou avancée avec leaqC'est la règle qui transforme le décalage 24 de "8 octets" en "pointer".

Quatre types de preuves de démarches

infer_layout()fusionne les accès de chaque fonction témoin et recueille le pas de quatre sources, chacune avec l'instruction qui l'a produit.

KindWhere it comes fromBuild that shows it
scalean Index with scale above 1 reached a pointer, from shlq $0x5 or imulq-O0, shlq $0x5, %rcx at 0x59
loop-stepa 64-bit increment of the base or index register inside a loop that also contains the access-O1, addq $0x20, %rdx at 0x24
offset-deltasame-width accesses through one base register inside one loop, at offsets a constant apart-O2, eight loads 32 apart
call-sitethe offsets at which one frame object is passed to the witness functions-O0, main passes rows, rows+0x20, rows+0x40

Une boucle est une boucle vers l' arrière .jmpLa valeur de l'augmentation est calculée en fonction de la valeur de l'augmentation ou de la branche conditionnelle, et un increment ne compte que lorsque la plage de boucle contient à la fois l'augmentation et l'accès.addl $0x1, %eaxqui avance ià-O0, parce que c' est un add-on de 32 bits et parce que l' accès ne passe pas par %eaxLa taille est la taille lorsque la mesure est au moins aussi grande que la fin du dernier champ.

La boucle vectorisée à -O2

À -O2Clang vectories sum_balancesLe corps de la boucle de0x50à 0xaaIl y a huit éléments par passe:

output
      3b: 48 8d 87 e4 00 00 00         	leaq	0xe4(%rdi), %rax
      50: 66 0f 6e 50 80               	movd	-0x80(%rax), %xmm2
      55: 66 0f 6e 98 60 ff ff ff      	movd	-0xa0(%rax), %xmm3
      61: 66 0f 6e 90 40 ff ff ff      	movd	-0xc0(%rax), %xmm2
      69: 66 0f 6e a0 20 ff ff ff      	movd	-0xe0(%rax), %xmm4
      7d: 66 0f 6e 10                  	movd	(%rax), %xmm2
      81: 66 0f 6e 58 e0               	movd	-0x20(%rax), %xmm3
      8a: 66 0f 6e 50 c0               	movd	-0x40(%rax), %xmm2
      8f: 66 0f 6e 60 a0               	movd	-0x60(%rax), %xmm4
      a0: 48 05 00 01 00 00            	addq	$0x100, %rax
      aa: 75 a4                        	jne	0x50 <sum_balances+0x40>

%raxcommence àlist + 0xe4Les huit déplacements placent les charges à des compensations.0x04, 0x24, 0x44, 0x64, 0x84, 0xa4, 0xc4, 0xe4de list, qui sont 4, 36, 68, 100, 132, 164, 196, 228- L' augmentation est0x100Un passe qui enregistre les compensations en les voyant rapporte huit champs et un structure d'au moins 232 octets.

fold_unrolled()Dans une boucle, prenez les mêmes accès de largeur à travers un registre de base.dSi la boucle est de l'étapek * det il y en a exactement.kLes compensations distinctes, la boucle a été déroulée ktemps, et chaque accès est le même élément d'accès à l'offset (offset - lowest) mod d- Je vous en prie .256 = 8 * 32avec huit décalages, donc toutes les huit charges se plient sur le décalage 4, et les données de décalage enregistrentoffset-delta 32- Je suis là .unrolled 32, et loop-step 256La boucle de résidu scalaire à 0xe0ajoute scale 32de shlq $0x5, %rdxet loop-step 32de addq $0x20, %rdxLe plus grand divisor commun des cinq valeurs est 32.

La figure a un bouton qui débranche le pliage.-O2l'état est ce que voit un lecteur qui fait confiance au déplacement sans la boucle.

DWARF 5 Des bytes

Le -gconstruire account.ccontient huit sections de débogage. .debug_infoest de 493 octets, .debug_abbrev179, .debug_str- Je suis là..debug_str_offsets124, .debug_addr112, .debug_line312, .debug_line_str12, et .debug_frame264. L'introduction des listes de normes DWARF 5 .debug_str_offsets- Je suis là ..debug_addr, et .debug_line_strComme nouveau dans la version 5. Le lecteur dans code/main.pyIl faut les cinq premiers.

L'en-tête de l'unité

La section 7.5.1.1 de la norme donne l'en-tête complet de l'unité de compilation: unit_length- Je suis là .version- Je suis là .unit_type- Je suis là .address_size, et debug_abbrev_offsetLes 12 premiers octets de .debug_infosont:

output
e9 01 00 00   unit_length = 0x1e9 = 489
05 00         version = 5
01            unit_type = DW_UT_compile
08            address_size = 8
00 00 00 00   debug_abbrev_offset = 0

Le unit_typela longueur compte les octets après elle-même, de sorte que l'unité suivante commencerait à 4 + 489 = 493, la fin de la section. llvm-dwarfdump --debug-infoimprime les mêmes cinq valeurs sur son Compile Unit:la ligne, et les premiers tests analysent cette ligne et comparent.

Abréviations

La section 2.1 indique que chaque entrée d'information de débogage "se compose d'une balise d'identification et d'une série d'attributs"..debug_abbrev(section 7.5.3) qui donne le nom de la balise, indique si les enfants suivent, et énumère chaque attribut avec sa forme. Cet objet a 14 déclarations.

output
[12] DW_TAG_structure_type  DW_CHILDREN_yes
     DW_AT_name              DW_FORM_strx1
     DW_AT_byte_size         DW_FORM_data1
     DW_AT_decl_file         DW_FORM_data1
     DW_AT_decl_line         DW_FORM_data1
[13] DW_TAG_member           DW_CHILDREN_no
     DW_AT_name              DW_FORM_strx1
     DW_AT_type              DW_FORM_ref4
     DW_AT_decl_file         DW_FORM_data1
     DW_AT_decl_line         DW_FORM_data1
     DW_AT_data_member_location  DW_FORM_data1

parse_abbreviations()Il est écrit par code ULEB128, tag ULEB128, un enfant octet, puis des paires d'attributs et de formes jusqu'à un 0, 0La section 7.5.6 donne les codes de formulaire: data1est un octet, ref4est un décalage de 4 bytes par rapport à l'unité, strx1est un indice de 1 octet dans la table des compensations de chaîne, sec_offsetet data4sont 4 octets, addrxest un indice ULEB128 dans le tableau d'adresses, exprlocest une longueur ULEB128 et que de nombreux octets, et flag_presentn'a pas de bytes du tout.

Décoder le membre de la balance à la main

L' entrée de structure pour accountcommence à l' offset 0x195avec les octets 0c 15 20 00 03Le code abréviation est 12, la structure.DW_AT_nameest l' indice de chaîne 0x15- Je suis là .DW_AT_byte_sizeest 0x20- 32 ?DW_AT_decl_fileest 0, et DW_AT_decl_lineest 3, qui est la ligne de struct account {dans la source.

La deuxième entrée de membres commence à 0x1a3avec 0d 10 88 01 00 00 00 05 04- Le numéro de la liste:

  1. 0dest l'abréviation 13, un membre.
  2. 10est DW_AT_namecomme strx1: index de chaîne 16.
  3. 88 01 00 00est DW_AT_typecomme ref4: compensation 0x188à partir du départ de l'unité, qui est le DW_TAG_base_typeentrée nommée int- Je suis désolé .
  4. 00est DW_AT_decl_fileet 05est DW_AT_decl_line: ligne 5 de account.c- Je suis désolé .
  5. 04est DW_AT_data_member_location: compensation 4.

La section 5.7.6 indique qu'une entrée membre "peut avoir soit un attribut DW_AT_data_member_location soit un attribut DW_AT_data_bit_offset" et qu'une constante entière "est le décalage en octets à partir du début de l'entité contenant". Ce octet est le même nombre que le déplacement en addl 0x4(%rax), %ecx, et compare_layouts()est la fonction qui met les deux côtés.

Les chaînes et les adresses passent par deux tables

L' index de chaîne 16 n' est pas un décalage vers .debug_strLe lecteur prendDW_AT_str_offsets_basede l'entrée unitaire, qui est 8, et lit l'entrée de 4 bytes à 8 + 4 * 16dans .debug_str_offsetsCette entrée contient le décompte de"balance"dans .debug_str- La même indirection s'applique aux adresses.sum_balancescommence par les octets 07 07: abréviation 7, sous-programme, puis DW_AT_low_pccomme addrxIndex 7. L'entrée de 8 octets à 8 + 8 * 7dans .debug_addrest 0x30, l' adresse de la fonction dans .textLes six premières entrées de ce tableau sont les adresses des six lignes littérales.

Dans un objet relocalisable, aucune table ne contient ses valeurs finales..debug_str_offsetsest zéro dans le dossier, et .rela.debug_str_offsetsdétient 29 R_X86_64_32Les délocalisations contre .debug_stravec le réel compensation dans l'addendum. .debug_addrIl a 13 R_X86_64_64Les délocalisations contre .textet .rodata.str1.1- Je suis là ..debug_infoa quatre délocalisations propres: l'abréviation compensation, DW_AT_str_offsets_basecomme .debug_str_offsets+0x8- Je suis là .DW_AT_stmt_list, et DW_AT_addr_basecomme .debug_addr+0x8- Je suis là .ElfObject.relocated()est appliquée S + APour les types 32 et 64 bits avant que le lecteur analyse une section. Un symbole de section a une valeur 0 dans un fichier objet, de sorte que le champ reçoit l'addend. llvm-dwarfdumpIl est donc possible de modifier le code de référence pour les données de référence.

Types, fonctions et lieux

DwarfInfo.type_name()suivant DW_AT_typeLes références.labelpoints de membre à un DW_TAG_pointer_type, qui indique uneDW_TAG_const_type, qui indique que le chartype de base, le nom se compose donc de const char *- Je suis là .type_size()Retour DW_AT_byte_sizepour les types de base et les structures, 8 pour un pointeur, et compter les temps de taille des éléments pour un tableau. type_kind()réduit leDW_AT_encodingde type de base à intavec un signe, ou float, et un type de pointeur à pointer, qui est le vocabulaire utilisé par la comparaison.

DwarfInfo.functions()Il est 6 .DW_TAG_subprogramLes entrées. bumpIl s' en va de 0x0à 0x21, qui est DW_AT_low_pcplus le DW_AT_high_pcSa base de cadre est DW_OP_reg6 RBP, et son paramètre aa l' expression de localisation 91 78Le numéro de la liste:DW_OP_fbregavec la valeur SLEB128 0x78, qui est -8. C'est la fente du cadre -0x8(%rbp)dans lequel la liste est stockée %rdià l' adresse 0x4Les informations de débogage et les instructions décrivent la même fente de deux côtés.

Comparons les deux conceptions

compare_layouts()Il est possible de trouver un champ déduit à la même distance.no-evidenceUn champ de taille différente estsize-mismatchUn membre de pointeur dont le champ n' a jamais été dérivé estpointer-unproven. Un type qui ne partage pas l'avis, tel qu'un flot où les instructions utilisent l'arithmétique des nombres entiers, est kind-mismatchLes champs inférieurs qui commencent à l'intérieur d' un membre ou à l' intérieur du rembourrage sont unexpectedLa taille compare le pas avec DW_AT_byte_size- Je suis désolé .

Les chiffres des trois constructions, de python3 code/main.py- Le numéro de la liste:

BuildAccesses through the first parameterFieldsStride evidenceMembers matchedSize
-O0115scale 32, call-site 32, call-site 645 of 532, match
-O195loop-step 325 of 532, match
-O2175 after folding, 12 beforeoffset-delta 32, unrolled 32, loop-step 256, scale 32, loop-step 325 of 532, match

Les espaces déduits sont des octets de 1 à 3 et de 10 à 15 dans chaque construction, et ce sont exactement les octets de remplissage de la mise en page DWARF. Chaque champ déduit se trouve à un multiple de sa propre taille, ce qui est ce que les règles psABI exigent, et natural_alignment_ok()Ça, ça, ça.

Ajoutez les fonctions une à la fois et l'image change.bumpseule la disposition est un champ à l'offset 4, la taille est inconnue, et la limite inférieure est 8. sum_balancesajoute le pas. tagajoute les compensations 0 et 8. projectedajoute le flot à 16. label_lengthajoute le pointeur à 24 et prouve qu'il s'agit d'un pointeur. mainajoute les sites d'appels à -O0et ne ajoute rien à -O1et -O2La figure passe par cet ordre pour chaque build.

Trois choses restent hors de portée.constLe type pointee de l'appareil est un type de pointee.labelest seulement "éléments 1 octet de l'autre" de la leaq 0x1(%rcx), %rcxdans la boucle de chaîne, ce qui est cohérent avec charet avec unsigned charet _BoolÀ la .-O1Le signe du octet à l'offset 0 est inconnu, car cmpb $0x6c, (%rdi)Les rapports de passage int8là et i8à-O0Où ?movsblLes faits sont les faits de l'étude 35 enregistrés comme inconnus.

Objets C++

shapes.cppdéfinit Shapeavec un destructeur virtuel, un pur virtuel area(), et une virtuelle name(), et Rect, qui dérive de Shapeet ajoute deux intL'ABI Itanium C++ régit comment clang les déploie sur x86-64 Linux.

Nom déformé

L'article 5.1.2 de l'ABI déclare qu'"un nom est déformé par le préfixe _ZL'objet a 19 symboles de ce type._ZNK4Rect4areaEvIl est écrit:_ZAlors ...Npour un nom encastré, Kpour une fonction de membre const, 4Rectet 4areacomme noms de source à longueur préfixée, Epour fermer le nom de l'emplacement, et vpour une liste de paramètres vide. llvm-cxxfiltdes empreintesRect::area() const- Je suis là ._Z8describeRK5Shapeiest describe(Shape const&, int)Le numéro de la liste:Rest une référence, Kest const, 5Shapeest la classe, iest intLes noms spéciaux de la section 5.1.4 sont _ZTVpour une table de travail, _ZTIpour typeinfo, et _ZTSPour la chaîne de noms typeinfo.C1- Je suis là .C2- Je suis là .D0- Je suis là .D1, et D2Je suis un homme ._ZN4RectD0Evet _ZN4RectD2Evà la fois imprimé comme Rect::~Rect(): le destructeur de suppression et le destructeur d'objet de base partagent un nom de source.

Demanglerdans code/main.pycouvre ce sous-ensemble plus les codes de type de construction de la section 5.1.5.2 et le S_Les résultats de l'essai sont comparés aux résultats obtenus.llvm-cxxfilt --no-strip-underscoreLe drapeau est important sur macOS, où l'outil enlève un sous-titrage principal par défaut et ne reconnaît pas ensuite Z8describe...- Je suis désolé .

Le vtable de ses délocalisations

La section 2.5.2 de l'ABI énumère les composants d'une table virtuelle dans l'ordre: les compensations d'appels virtuels, les compensations de base virtuelles, les compensations vers le haut, le pointeur typeinfo, puis les pointeurs de fonction virtuelle. Le point d'adresse, la valeur de vptr d'un objet, est l'adresse du premier pointeur de fonction (section 2.6.2). Rectn'a pas de bases virtuelles, donc sa table est compensée par le haut, typeinfo et les fonctions.

Le symbole_ZTV4Rectest de 0x30 octets, six espaces de 8 octets. Dans l'objet chaque octet est de zéro..rela.data.rel.ro._ZTV4Rect- Le numéro de la liste:

output
0000000000000008 R_X86_64_64              _ZTI4Rect
0000000000000010 R_X86_64_64              _ZN4RectD2Ev
0000000000000018 R_X86_64_64              _ZN4RectD0Ev
0000000000000020 R_X86_64_64              _ZNK4Rect4areaEv
0000000000000028 R_X86_64_64              _ZNK4Rect4nameEv

La fente 0 maintient le décalage vers le haut, ce qui est 0 et ne nécessite aucun déménagement.+16De là, la fente 0 est le destructeur complet de l'objet, la fente 1 est la destruction de la suppression, la fente 2area, point 3 name- Je suis là .read_vtable()Il lit la taille du symbole, les délocalisations dans sa plage et le zéro à l'écart 0. ShapeLa table a la même forme que __cxa_pure_virtualdans la fente 2, qui est la fonction de temps d'exécution qu'une pure appel virtuel atteint.

RTTI

La section 2.9.4 décrit std::type_infocomme un vptr suivi d'un pointeur vers le nom du type mangled, et énumère les classes dérivées. _ZTI4Recta trois délocalisations: _ZTVN10__cxxabiv120__si_class_type_infoE+0x10- Je suis là ._ZTS4Rect, et _ZTI5ShapeLa première est la table de__si_class_type_info, la classe utilisée par l'ABI pour un type avec une base non virtuelle publique unique à l'offset zéro, et le +0x10est le point d'adresse de cette table.4Rect, qui est le nom du type manglé, et name()sur unetype_infoLe troisième est la base._ZTI5Shapeutilisations __class_type_infoet n'a pas de pointeur de base. read_typeinfo()Retourne ces trois faits.

L'appel virtuel

describeà-O0fait un appel virtuel:

output
       f: 48 8b 7d f8                  	movq	-0x8(%rbp), %rdi
      13: 48 8b 07                     	movq	(%rdi), %rax
      16: ff 50 10                     	callq	*0x10(%rax)

La première .movqLe second lit 8 octets à l'offset 0 de l'objet, qui est le vptr, parce que l'ABI le place en premier dans une classe avec des fonctions virtuelles.callqpasse par là .0x10Le rapport de l'Agence de sécurité a été publié en décembre.0x10 / 8est la tranche 2.find_virtual_calls()correspond au modèle et rapporte les deux candidats: Rect::area() constde _ZTV4Rectet __cxa_pure_virtualde _ZTV5ShapeLequel de ces deux sort dépend de l'objet, c'est pourquoi l'appel est indirect.

Le DWARF enregistre la même position.DW_TAG_subprogramles entrées pour areadans les deux classes de transportDW_AT_vtable_elem_location (DW_OP_constu 0x2)- Je suis là .nameLes destructeurs ont la fente 0.Rectest une DW_TAG_structure_typede 16 octets avec un DW_TAG_inheritanceentrée pour Shapeà l' offset 0 et membres wà 8 heures et hÀ 12 heures.Shapeest de 8 octets avec un membre artificiel _vptr$Shape. Deux dérivées indépendantes donnent la place 2 pour area: le tableau de relocalisation et les entrées de débogage.

À -O1L'objet n'a pas de vtable du tout. mainconstructions Rectcomme un local, donc le compilateur résolu r.name()et describe(r, 2)La table n'est donc pas émise. describecontient encore movq (%rdi), %raxet callq *0x10(%rax), parce que son paramètre peut être n' importe .ShapeUne table vtable manquante est un fait sur un fichier objet, et le test test_the_optimised_object_keeps_the_call_pattern_but_drops_the_vtablesVérifie les deux parties.

Le Microsoft ABI diffère dans chacun de ces détails.?et encoder la convention d'appel, donc void test(void)Compilée en C++ __cdeclest ?test@@ZAXXZLe localisateur RTTI est situé à vfptr[-1]comme une _RTTICompleteObjectLocator, et les symboles sont ??_7pour un vftable et ??_R4La documentation de Microsoft dit qu'il n'y a pas de normes pour le nommage en C++ entre les fournisseurs de compilateurs.

Comment les outils présentent les types récupérés

Chaque ligne ci-dessous qui donne un nom a été produite sur cette machine et capturée dans code/fixtures/tools/Les lignes pour les outils qui ne sont pas installés ici décrivent leur documentation et le disent.

ToolVersion, licenceWhere types liveOn our fixtures
Ghidra12.1.4, 2026-09-21, Apache-2.0Data Type Manager with built-in, user-defined, and derived types; .gdt archives; types used by a program are copied into itWith DWARF: /DWARF/account.c/account size=32 align=8 and a->balance = by + a->balance;. Without: *(int *)(param_1 + 4) with long param_1, tag(char *param_1), and *(char **)(param_1 + 0x18)
IDA Pro9.4, 2026-07-13, commercial; IDA Free is non-commercial with a cloud decompiler for x86 onlyLocal Types window, .til type libraries, DWARF and PDB import into a dwarf or pdb folderNot installed. Documentation only
Binary Ninja6.0, 2026-09-03, commercial with a Free editionType editing, header import and export, type archives, debug info from DWARF and PDB, type libraries, platform typesNot installed. Documentation only
radare26.2.4, 2026-10-05, LGPLv3ts, tsc, afvdts and tsc account printed nothing for the relocatable object with bin.dbginfo=true; pdf shows mov dword [rax + 4], ecx with source line comments from DWARF
rizin0.9.1, 2026-06-29, LGPL corets, tsc, afvdtsc account printed struct account { char kind; int balance; short code; double rate; const char *label; }; from DWARF; the build without -g lists only its bundled header types
lldblldb-1703.0.236.21image lookup -tbyte-size = 32 and the full compiler_type text from the ELF object
gdbnot installed hereptype /o prints "the offsets and sizes of fields in a struct" with holes markedDocumentation only
pyelftools0.33, public domainPython API over .debug_infoA 43-line script prints the same five members and unit version 5
llvm-dwarfdumpApple LLVM 17.0.0--debug-info, --debug-abbrev, --debug-str-offsetsThe oracle for every reader test

La sortie de Ghidra sans DWARF montre les mêmes deux règles en fonction.char *à la valeur de tagparce que la fonction compare un octet à travers elle, et char **sur le terrain à 0x18Parce quelabel_lengthdéference la valeur chargée et compare les octets. Ce sont les mêmes deux règles que le passage de cette leçon: largeur de l'instruction, pointeur de la dériverence. La documentation du décompilateur de Ghidra indique que les varnodes de code p "n'ont pas nécessairement un type de données associé à eux" et que le décompilateur "attribue finalement un type de données formel" par propagation. L'option "Inférer des pointeurs constants" étend la même propagation aux constantes. Ghidra 12.1 a ajouté la récupération de bitfield au décompiler, ce que les fixtures de cette leçon n'exercent pas.

La table faite à la main est l'autre moitié de l'histoire.tdTout le monde vous laisse taper le struct de la mise en page récupérée et regarder le décompiler rediriger.

réa 6.5.0 avec son fournisseur Ghidra renvoie le texte du décompilateur en tant qu'enregistrement. Tout ce qui est dans la ligne Ghidra s'applique à elle, et l'enregistrement porte les noms de type sans la preuve qui les a produits.python3 code/main.py infersur la même liste vous donne cette preuve.

Recherche de type d'inférence

Le passage ici est la moitié de la génération de contraintes d'un système d'inférence de type, limité à un struct atteint à partir d'un paramètre.

  • TIE (Lee, Avgerinos, Brumley, NDSS 2011) a cadré la reconstruction de type comme génération de contraintes et résolution sur une IR binaire, a mesuré la conservatrice et la précision, et comparé avec un décompilateur commercial sur 87 programmes de base.
  • Retypd (Noonan, Loginov, Cok, PLDI 2016) a ajouté des sous-types, des polymorphismes et des types récursifs, et a rapporté une reconstruction du pointeur constUne mise en œuvre open source existe sous le nom de GrammaTech.
  • OSPREY (Zhang et autres, IEEE S&P 2021) traite chaque emplacement de mémoire comme une variable aléatoire et infère les types les plus probables.
  • DIRTY (Chen et autres, USENIX Security 2022) et ReSym (Xie et autres, CCS 2024) produisent des décompiles post-processus avec des modèles apprentis, et ReSym rapporte une précision de 72,9% sur les mises en page complètes de structures.
  • RecStruct (Chen et autres, USENIX Security 2026) récupère les structs nichés sans décompiler, avec une exécution symbolique insensible au flux et une union-trouver sur les fentes de pile.

La structure de pile est la première chose que cette leçon passe.-O1- Je suis là .mainIl le garde .rowssur la pile et le code écrit en ligne0x24(%rsp)- Je suis là .0x40(%rsp)- Je suis là .0x48(%rsp)- Je suis là .0x30(%rsp), et 0x38(%rsp)Ces modules 32 sont 4, 0, 8, 16 et 24: le même structure, sans paramètre de semence.

Faites-le

Exécutez la démo:

bash
cd phases/13-tools-and-protocols/41-recovering-data-and-types
python3 code/main.py

La sortie est un rapport JSON avec cinq parties. dwarfdétient l'en-tête de l'unité, les structures et les fonctions de l'unité -O0objet.oraclescontient la disposition des règles psABI, le ctypesL'élaboration de la mise en page, la mise en page DWARF, la mise en œuvre native et un agreeLe drapeau.buildsIl contient les accès, la disposition déduite et la comparaison pour chacun des trois objets. cxxcontient les noms démêlés, les deux vtables, les deux enregistrements typeinfo, l'appel virtuel et les fentes DWARF. toolsliste des sorties de l'outil capturées.

Forme attendue:

json
{
  "dwarf": {"unit": {"unitLength": 489, "version": 5, "unitType": "DW_UT_compile", "addressSize": 8, "abbrevOffset": 0}, "...": "..."},
  "oracles": {"agree": true, "psabiRules": {"size": 32, "align": 8, "...": "..."}, "...": "..."},
  "builds": {
    "O0": {"layout": {"stride": 32, "size": 32, "gaps": [1, 2, 3, 10, 11, 12, 13, 14, 15], "...": "..."}, "comparison": {"matches": 5, "members": 5, "...": "..."}},
    "O1": {"...": "..."},
    "O2": {"...": "..."}
  },
  "cxx": {"virtualCalls": [{"slot": 2, "candidates": {"Shape": "__cxa_pure_virtual", "Rect": "_ZNK4Rect4areaEv"}, "...": "..."}], "...": "..."}
}

Lire code/main.pydans cet ordre:

  1. ElfObjectIl lit des sections, des symboles et des délocalisations, et relocated()est appliquée S + Aà une section de débogage.
  2. parse_abbreviations()et DwarfInfo._parse_unit()Lisez l'en-tête, la table des abréviations et les entrées, puis résolvez strxet addrxà travers les deux tables.
  3. DwarfInfo.struct_layouts()et functions()transformer les entrées en StructLayoutet FunctionInfoles documents, avec type_name()- Je suis là .type_size(), et type_kind()- Je suis désolé .
  4. parse_listing()- Je suis là .parse_memory(), et access_width()Lire le llvm-objdumpJe vous ai envoyé un SMS.
  5. trace_function()est la machine de l'état abstrait, et fold_unrolled()et infer_layout()transformer les accès en un plan.
  6. compare_layouts()produit les lignes de statut, et sysv_layout()et ctypes_layout()sont les deux autres oracles.
  7. Demangler- Je suis là .read_vtable()- Je suis là .read_typeinfo(), et find_virtual_calls()couvrir l'objet C++.

Il y a quatre commandements.python3 code/main.py dwarf FILE.oimprime les structures et les fonctions de tout objet relocalisable DWARF 5. python3 code/main.py infer LISTING.txt [FILE.o]Il utilise le passe dans une liste et compare avec un objet son DWARF. python3 code/main.py demangle NAME...et python3 code/main.py vtable FILE.ocouvrir la moitié de C++.

Utilisez-le

Sur un nouvel objet que vous avez construit vous-même:

  1. Capture llvm-objdump -d -rLe passe lit ce texte.
  2. On court .python3 code/main.py inferLire la ligne témoins: elle nomme les fonctions dont le premier paramètre a été utilisé comme base de mémoire. Si une fonction manque, son paramètre n'a jamais atteint un opérand de mémoire, ou il a atteint un par un chemin que le passage ne suit pas.
  3. Lisez chaque champ avec son nombre d'accès.
  4. Lisez les preuves de la marche.call-siteou scaleL'entrée est forte.loop-stepL'entrée sans autre type est faible, car une boucle déroulée rapporte l'étape de plusieurs éléments.unrolledIl apparaît, vérifiez le pli à la main.
  5. Si une-gconstruire existe, faire la comparaison et lire chaque ligne qui n'est pas match- Je suis un .no-evidencela ligne nomme un membre sans fonction touché.unexpectednom de rangée à un accès que le passe ne pouvait pas placer.
  6. Tapez la disposition dans le gestionnaire de type de votre outil comme un struct, et enregistrer quels champs, noms et types sont des devisions.

Gardez les trois oracles à portée de main.ctypes, et DWARF ne sont pas d'accord sur une mise en page que vous avez écrite, vous avez trouvé un struct chargé, un bit-field, ou une cible avec des règles différentes.

Laboratoire interactif

Laboratoire A: décodez l'entrée membre

De la coderépertoire, ouverture Python:

bash
cd phases/13-tools-and-protocols/41-recovering-data-and-types/code
python3 -q

Je vais courir .

python
from main import *
elf = load_object("account-x86_64-O0-g.o")
dwarf = DwarfInfo(elf)
dwarf.header()
elf.section(".debug_info").data[0x1a3:0x1ac].hex(" ")
dwarf.dies[0x1a3].attributes
dwarf.type_name(dwarf.type_of(dwarf.dies[0x1a3]))

Décodez les neuf octets sur papier avec l'abréviation 13 avant de regarder le dictionnaire.elf.section(".debug_str_offsets").data[8:16].hex(), et celui qui a été déplacé,elf.relocated(".debug_str_offsets")[8:16].hex(), et expliquer la différence.

Laboratoire B: tracer une fonction

python
listing = load_listing("account-x86_64-O0-g.objdump.txt")
trace = trace_function("label_length", listing["label_length"])
[(hex(a.address), a.root, a.offset, a.width, a.kind) for a in trace.accesses]
trace.pointer_uses
trace_function("sum_balances", listing["sum_balances"]).loops

Avant d'exécuter, écrivez quel accès prouve que le décalage 24 est un pointeur.-O1la liste et trouver les leaq 0x1(%rcx), %rcxdans trace.increments- Je suis désolé .

Laboratoire C: plier la boucle vectoriée

python
o2 = load_listing("account-x86_64-O2-g.objdump.txt")
trace = trace_function("sum_balances", o2["sum_balances"])
sorted({a.offset for a in trace.accesses})
folded, evidence = fold_unrolled(trace, trace.accesses)
sorted({a.offset for a in folded}), [(e.kind, e.value, e.text) for e in evidence]

Alors , courez .analyze_build("O2", fold=False)["comparison"]et compter le unexpectedLes lignes. Modifiez la règle de pliage pour que l'étape de la boucle soit exactementk * davec kau moins 2, et confirmer que le -O1boucle, avec un accès et l'étape 32, est laissé seul.

Laboratoire D: le tableau et les noms

python
shapes = load_object("shapes-x86_64-O0-g.o")
read_vtable(shapes, "_ZTV4Rect")
read_typeinfo(shapes, "_ZTI4Rect")
demangle("_ZNK4Rect4areaEv"), demangle("_Z8describeRK5Shapei")
find_virtual_calls(load_listing("shapes-x86_64-O0-g.objdump.txt"), [read_vtable(shapes, "_ZTV5Shape"), read_vtable(shapes, "_ZTV4Rect")])

Calculez la fente à partir de callq *0x10(%rax)sur papier, puis confirmez contre DwarfInfo(shapes).struct("Rect").virtual_methods- Alors chargez .shapes-x86_64-O1-g.oet essayezread_vtableJe vais lire le livre.ElfErroret expliquer pourquoi le symbole est absent.

Laboratoire de pratique

Récupérer la structure de pile en mainà-O1- Je suis désolé .

Les exigences:

  • Extension trace_function()Ainsi, une région de la fente de cadre peut être une racine.movapsLes magasins qui se remplissent 0x0(%rsp)à 0x5f(%rsp)de .data.rel.ro, qui est l' initialisateur de 96 octets de rows- Je suis désolé .
  • Attribuer les accès en ligne 0x24(%rsp)- Je suis là .0x40(%rsp)- Je suis là .0x48(%rsp)- Je suis là .0x30(%rsp), et 0x38(%rsp)Je suis en train de faire une demande de répartition de la part de la Commission.
  • Dériver l' étape 32 de la boucle à 0x120à 0x12cOù ?%r14commence à 4 et avance à 0x20jusqu' à0x64, et montrer que la boucle est liée 0x64est trois éléments plus le champ de compensation.
  • Comparez le résultat avec la mise en page DWARF et atteignez cinq correspondances, et enregistrer les types restent non prouvés.
  • Ajoutez un test qui échoue lorsque la racine du cadre est attribuée à un paramètre.

Le succès signifiepython3 code/main.py infersur le -O1Liste de référencement maincomme un témoin avec une racine de cadre, et la comparaison montre cinq correspondances de mainJe suis seule.

Leçon d'artisanat

Cette leçon comprend outputs/skill-recovering-data-and-types.md. Utilisez-le pour transformer une liste et une configuration de débogage optionnelle en une mise en page structurelle avec des preuves par champ, un pas avec ses sources, une comparaison avec DWARF et une liste de ce qui est resté inconnu. Il nécessite la table d'accès, la preuve des pas, la vérification du pli, l'oracle DWARF lorsqu'il existe, et une déclaration de noms et de qualifications qu'aucune instruction ne peut fournir.

Vérifiez

Exécutez la démo et la suite déterministe:

bash
cd phases/13-tools-and-protocols/41-recovering-data-and-types
python3 code/main.py
python3 -m unittest discover -s code/tests -v

La vérification doit prouver:

  • L'en-tête de l'unité, les 14 abréviations et les cinq composantes de l'offset sont d'accord avec llvm-dwarfdump
  • Les noms arrivent par les délocalisés .debug_str_offsetstable et de l'unité str_offsets_baseet addr_basesont 8
  • six fonctions portent les gammes, DW_OP_reg6 RBP, et DW_OP_fbregles emplacements que le dépôt affiche et leurs adresses basses équivaut à la fonction de mise en liste démarre
  • un objet sans .debug_infoLe décodeur LEB128 est utilisé.0x78comme -8
  • pyelftoolset lldbimprimer la même disposition que le lecteur
  • les règles de la PSABI, ctypes, DWARF, et la course native donner des compensations 0, 4, 8, 16, 24et la taille 32, et le struct réorganisé est de 24 octets
  • Le parseur de liste lit 13 instructions dans bump, attaches R_X86_64_PLT32 bump-0x4à l' appel à 0x194, et trouve le saut en arrière à 0x72
  • Opérands de mémoire, largeurs, types et signes décodés du texte AT&T et du mnemonique
  • bumpà-O0lis son pointeur à l'arrière de la fente du cadre et fait trois accès à l'offset 4
  • Le pas vient de shlq $0x5à-O0, à partir de addq $0x20, %rdxà-O1, et à partir de huit charges pliées par un pas de 256 à-O2
  • label_lengthprouve le pointeur à l'offset 24 en le déférencant
  • mainpasse un objet de cadre à des décalages 0, 0x20, et 0x40à-O0et ne appelle que printfà-O1
  • chaque construction correspond à cinq des cinq membres avec taille 32 et les espaces égaux au rembourrage DWARF
  • sans plier le -O2construire des rapports sept champs passé par byte 32 et aucune taille
  • Le démangeur reproduit les 19 llvm-cxxfiltles chaînes
  • les deux tables vtables, les deux enregistrements typeinfo, et l'appel virtuel à 0x16résoudre à la fente 2, et DWARF est d'accord
  • le -O1L'objet C++ conserve le modèle d'appel et n'a pas de vtable
  • Les captures de Ghidra, rizin et radar2 montrent la structure avec DWARF et les compensations brutes sans elle
  • la figure interactive intègre les mêmes données que l'analyse produit

Mode de défaillance de la production

FailureWhat the weak tool reportsWhat the analysis must prove
Unrolled loopEight fields 32 bytes apart and a 232-byte structLoop step 256 equals 8 times the offset delta 32, so one field at offset 4
Stride read from one incrementsizeof 256 from addq $0x100The greatest common divisor of every stride source, with the scalar tail's addq $0x20
8-byte field called a pointervoid * at offset 24A later access uses the loaded value as a base, or the kind stays int64
Pointer called an integerlong at offset 24The dereference at 0xfb, recorded as pointer proof
Narrow comparechar with a signcmpb carries no sign; only movsbl or movzbl does
Five functions assumed to share a typeOne struct with no evidence of aliasingCall sites in main that pass one frame object, or an explicit analyst assertion
Strings read from .debug_str by indexGarbage names from a relocatable objectstr_offsets_base plus the relocated .debug_str_offsets entry
Addresses read as numberslow_pc of 0 for every functionaddrx through the relocated .debug_addr table
DWARF 4 objectA reader that misparses the headerVersion check on the second header field; 4 has no unit_type byte
Missing vtable"the class has no virtual functions"The -O1 object inlined every call; the -O0 object has the table and its relocations
Vtable slot read from the wrong baseOff-by-two slot numbersThe address point is 16 bytes past the symbol, after offset-to-top and typeinfo
Mangled name strippedZ8describeRK5Shapei left as is--no-strip-underscore on macOS, or a demangler that checks for _Z
Struct typed from one tool's guesschar *param_1 taken as the field typeThe width and the dereference, written as evidence, and the name marked as a guess

Règlement d'exploitation

Récupérer un champ d'une accès et un pas d'une boucle, et joindre l'instruction à chacun. Faites des boucles déroulées avant de faire confiance à un décalage passé par la marche. Lisez DWARF à travers ses abréviations et ses tableaux déplacés, jamais par indexation dans une section de chaîne. Comparez champ par champ et écrivez chaque membre sans preuve, chaque pointeur non prouvé et chaque nom comme inconnu. Dans un objet C++, lisez la table v à partir de ses délocalisations et la fente à partir de l'appel, puis utilisez DWARF pour confirmer la fente que vous lisez.

Pour en savoir plus

This free lesson is part of the AI Engineering from Scratch curriculum. Read the full explanation, run the lesson code, and verify the result in the interactive reader or from the repository source.

Browse the complete course catalog or open this lesson on GitHub.