TL;DR
python extension of gdb and pyelftools
module I used it to [implement] the backtrace (https://gist.github.com/sumomoneko/d8415b14f8eddf74a3eb9bd6d521fab3).
It may be useful in environments where the gdb backtrace
command does not work as is.
When developing at Kumiko Michiho,
--The gdbserver that came with ICE is somehow buggy, and gdb crashes with backtrace
.
--Since gdb uses a thread implementation that it doesn't know, backtrace for each thread is not possible.
--Gdbserver, which has only binary, knows the thread structure, but if you change the code around the thread, the structure will shift and the gdb will drop.
--I got a raw memory dump, but gdb for this platform doesn't have the ability to read dump files in the first place.
Isn't that the situation that is rarely common? When I was in trouble because I was thrown into the savanna with just such serials and LEDs, thanks to How to walk debug information, I became a friend who can read Now that I'm used to it, I'd like to backtrace it.
For the sake of simplicity of the follow-up test, I have confirmed it in the x64 ubuntu 16.04 gcc5.4 / python3.5 environment instead of the embedded environment [^ 1].
[^ 1]: As a built-in, we are putting it into actual battle with RX.
The basics of DWARF are explained in great detail in How to walk debug information, so please refer to that. Also, [here](https://ja.osdn.net/projects/drdeamon64/wiki/DWARF%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3% The article 83% 95% E3% 82% A9% E3% 83% BC% E3% 83% 9E% E3% 83% 83% E3% 83% 88) was also very helpful.
Also, I won't go into too much detail about gdb Python-API. I'm not doing complicated things with the python gdb
module, so I think you can get it by looking at the sample.
pyelftools
Decoding DWARF with bare hands is difficult, so install the python module pyelftools that decodes a considerable part. I want to isolate the environment with virtualenv etc., but when I call python via gdb, it doesn't look at the module path in virtualenv, so I put it directly in the system.
$ sudo pip3 install pyelftools
Alternatively, expand the module somewhere and put it at the beginning of your python script
import sys
sys.path.append("/home/oreore/virtualenv/python/site-packages")
You may enter the path like this [^ 2].
[^ 2]: Please point out if there is a brilliant solution that can persuade gdb.
Use affordable code to see how it works. Here, we will use the Mach compression / decompression library lz4 and its test application simpleBuffer
. For example, here explains what lz4
is, so please refer to it.
Now, clone and build as follows.
$ git clone https://github.com/lz4/lz4.git
$ cd lz4
$ CFLAGS="-g3 -fno-dwarf2-cfi-asm" make all
As a little supplement to the build flag, in gcc (Ubuntu 5.4.1-2ubuntu1 ~ 16.04) 5.4.1 20160904 I tried here, gas CFI directive /as/CFI-directives.html) seems to omit the section .debug_frame
where the stack frame information is stored, and only its simplified version (?) .Eh_frame
came out.
pyelftools
also supports decoding .eh_frame
, but it takes some time to use. So this time, I'm asking the compiler to output .debug_frame
-fno-dwarf2-cfi-asm
options I requested it with .html). This may have changed with recent ubuntu / gcc.
First, run it with gdb, break at the appropriate part, and backtrace.
$ 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
Let's reproduce this.
I have prepared the completed one in gist, so I would like to explain it accordingly.
I'd like to start analyzing DWARF right away, but before that, I'll organize the execution environment.
This time, we will create a python script that runs on x64 gdb, but it is not essential to run it on gdb separately. Also, due to the nature of DWARF, it does not mean that it must be x64. If the following functions are prepared, it can be executed as a single python script independently of gdb and architecture.
--Get the file path that contains DWARF --Read memory from the specified address --Get the program counter of the backtrace start point (program stop location) --Get the register value at the time of stop --Set a value for the stack pointer
First, let's separate these functions as functions so that we can easily change the target later. The implementation of each function is explained below.
Get the path of the executable that is not stripped. It takes the name of the object running under gdb (called "inferior" in gdb). If it is built-in, it will be a binary file before strip that was saved before burning to ROM.
def get_elf_file_name() -> str:
"""
Get the filename that contains the debug information.
Under gdb environment, gdb.objfiles()[0].Found in filename
:return:File path with debug information
"""
import gdb
return gdb.objfiles()[0].filename
uintptr_t ret = *reinterpret_cast<uintptr_t*>(addr);
Here is an image of doing this with gdb-python. It is used to fetch the memory pointed to by the register. Since the stack area is required as the memory area, it is good to at least set the stack area as the dump target when creating your own dump.
def read_uintptr_t(addr: int) -> int:
"""
uintptr_Read t size data from memory
uintptr_t ret = *reinterpret_cast<uintptr_t*>(addr);
:param addr:address
:return:data
"""
import gdb
uintptr_t = gdb.lookup_type('uintptr_t')
return int(gdb.Value(addr).reinterpret_cast(uintptr_t.pointer()).dereference())
I just wrote (gdb) p $ pc
in python-gdb.
If you make your own dump, is it like saving the source address that jumped to exception handling?
def get_pc() -> int:
"""
Get the program counter
:return:PC value
"""
import gdb
return int(gdb.parse_and_eval("$pc"))
DWARF is designed for a variety of architectures. Therefore, registers are not treated with architecture-specific names, but are numbered and managed. Then, what register is what number? Quickly [source code of gdb](https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=blob;f=gdb Let's take a look at /amd64-tdep.c; h = b589d93940f1f498177ba91273190dc9b0714370; hb = HEAD # l156).
So here is the finished product. I've commented out the stN
and mmN
registers, but this is a slack because the wide registers were a hassle [^ 3].
If you're looking for a register number for a different architecture, you should look around for a similar file with a CPU name in a similar place in the gdb source tree.
[^ 3]: I originally wrote it for debugging embedded CPUs, so I don't have such registers.
def get_registers() -> List[int]:
"""
Collect register values from gdb according to the DWARF register number
https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=blob;f=gdb/amd64-tdep.c;h=b589d93940f1f498177ba91273190dc9b0714370;hb=HEAD#l156
:return:Register value list in order of DWARF register number
"""
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
As you traverse the call tree, you need to rewind the stack. Since the register that holds the stack is architecture-dependent, it is separated into functions as rewind processing.
def unwind_stack(regs: List[int], cfa: int) -> None:
"""
Set the address in the register that holds the stack pointer.
For x64$For rsp. The DWARF register number is 7, so set the address there.
:param regs:Array of registers(DWARF register number order)
:param cfa:Address to set as a stack pointer
:return: None
"""
regs[7] = cfa
That's it for gdb and architecture reliance.
The overall flow of the backtrace process is as follows.
This is written solidly with main ()
.
def main() -> None:
"""
Show backtrace
"""
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()
#For the first time from the current stop position information,
pc = get_pc()
regs = get_registers()
i = 0
while True:
#Get function information of stop position
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
#Look at the stack frame and follow the caller
pc, regs = get_prev_frame(fi["cu"], pc, regs, read_uintptr_t)
if pc is None:
break
"Getting the stop address, register (& stack pointer)" only calls the function explained in the previous section, so we will start with "Getting the function information including the address".
As a DWARF capture route to obtain a function that includes it from a certain address,
--Lick all CU / DIE and find a hit address range --Subtract DIE from the address
There are two ways. The latter is exactly the method for this purpose, [DWARFv4 6.1.2 Lookup by Address](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) Corresponding table to .debug_aranges
section Is written.
Unfortunately, pyelftools
doesn't support .debug_aranges
. So, I will frankly search for CU / DIE.
def get_func_info(dwarf_info: DWARFInfo, addr: int) -> Optional[Dict]:
"""
Get the function information including the address indicated by addr
:param dwarf_info:DWARF information
:param addr:Program counter
:return:Function information such as function name and address
"""
#From each compile unit
for cu in dwarf_info.iter_CUs():
#While iterating the DIE
for die in cu.iter_DIEs():
try:
#Looking for the DIE of the function,
if die.tag == 'DW_TAG_subprogram':
#Find the address range occupied by the function
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 if the specified address matches this function range
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
From all CompileUnits, search for DIE and iterate DIE DW_TAG_subprogram
which represents the function. The address range occupied by the function is [2.17 Code Addresses and Ranges](http://dwarfstd.org/doc/DWARF4.pdf#%5B%7B%22num%22%3A137%2C%22gen%22%3A0% The specifications are written in 7D% 2C% 7B% 22name% 22% 3A% 22XYZ% 22% 7D% 2C0% 2C792% 2Cnull% 5D).
If the attribute [DW_AT_low_pc
, DW_AT_high_pc
) indicating the interval indicates a single address range, or if there are multiple discontinuous ranges due to optimization etc., if the range is specified by DW_AT_ranges
There is.
For now, let's deal with only a single address range. If DW_AT_ranges
appears, implement it at that time.
From here, domain-specific words such as attributes and classes will be scattered, so I will organize them once.
DW_AT_TAG_ * code>.
- attribute (
DW_AT_ * code>)
- Information element contained in DIE. Names and address ranges.
- class
The meaning expressed by the
- attribute.
address code>, constant code>, string code>.
- format (
DW_FORM_ * code>)
- Shows how it is held as an entity.
DW_FORM_data2 code> is a 2-byte value, DW_FORM_data4 code> is a 4-byte value.
If you use these terms to describe how to find the start address of a function,
For the CU in main.c, if you follow from the top DIE, you will reach the main function DIE. The tag for this DIE is
DW_TAG_subprogram
and theDW_AT_name
attribute is "main". TheDW_AT_low_pc
attribute has a value of the ʻaddressclass in the form
DW_FORM_addr`. That is the start address of the main function.
It will be.
Now that we know the start address, let's look at the end side, the DW_AT_high_pc
attribute.
[2.17.2 Contiguous Address Range](http://dwarfstd.org/doc/DWARF4.pdf#%5B%7B%22num%22%3A137%2C%22gen%22%3A0%7D%2C%7B%22name% According to 22% 3A% 22XYZ% 22% 7D% 2C0% 2C792% 2Cnull% 5D), the DW_AT_high_pc
attribute can be of class ʻaddress or
constant. If it's class ʻaddress
, it's * the relocated address *, so you can think of it as an address in loaded memory [^ 4]. If the class is constant
, it means the offset address from DW_AT_low_pc
.
[^ 4]: I think ASLR is disabled when gdb works, so it should be the same as the asking price at the time of linking (I'm not confident because I don't think about shared libraries properly)
Now that you know the start and end addresses of this function, you can determine if this is the function you are looking for.
Now that I know the function name and address, what should I do with the source file?
[6.2 Line Number Information](http://www.dwarfstd.org/doc/DWARF4.pdf#%5B%7B%22num%22%3A350%2C%22gen%22%3A0%7D%2C%7B%22name% According to 22% 3A% 22XYZ% 22% 7D% 2C0% 2C792% 2Cnull% 5D), you can see that there is an address-to-source code conversion table in the .debug_line
section.
The basic idea is to have a reference table from the object's address to the source code file name, line, and column number. However, if you simply create such a table, it will be a huge table that is several times larger than the object as shown below.
address | Source file name | Number of lines | Number of digits |
---|---|---|---|
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 |
Therefore, DWARF uses the following two methods to reduce the storage size.
address | Source file name | Number of lines | Number of digits |
---|---|---|---|
0x00abcd | main.c | 10 | 8 |
0x00abce | main.c | 11 | 10 |
0x00abcg | main.c | 12 | 8 |
0x00abci | main.c | 13 | 8 |
Saves size by stripping duplicate lines when machine instructions are multiple bytes.
Design your own stack machine without recording tables and use it to save recording size.
_People, people, people, people, people, people, people, people, people, people
> Design a stack machine <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄
It suddenly became a super expansion, but fortunately pyelftools
moves the stack machine and expands it to the address / row number table, so I am grateful to use it. Actually, class Line Program The stack machine is running around here.
So, the function to get the source file name and the number of lines from the address is as follows.
def get_file_line_from_address(cu: CompileUnit, addr: int) -> Dict:
"""
Find the source code file name and line count information from the compile unit
:param cu:compile unit information
:param addr:Address in the object
:return:Source code file name and number of lines
"""
top_die = cu.get_top_DIE() # type: DIE
#The compile-time directory.
#The source path is shown relative to this directory
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:
#The address range of entry contained the address you were looking for
#Ask for the full path of the file
fe = line_program["file_entry"][entry.state.file - 1]
name = fe.name.decode('utf-8')
if fe.dir_index != 0:
#A different directory than the compile-time directory(Notated as a relative path)If there is a source in
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()
Well, here is the production. To go back to the call
I need this information.
The basic idea is, as usual, to have a huge table. For example
address | Caller's stack pointer | Register r0 to return | Register r1 to return | 呼び元address |
---|---|---|---|---|
0x100 | .. | .. | .. | .. |
0x101 | .. | .. | .. | .. |
0x102 | .. | .. | .. | .. |
0x103 | .. | .. | .. | .. |
By creating such a huge table, the information for returning to the caller can be restored at any address while the consumption status of the register stack changes every moment as the function progresses.
Here is a little definition of the term, but roughly define that the top of the stack pointer when returning to the function caller is called CFA. To be 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).
The memory area allocated on the stack is called "call frame". The call frame is identified as an address on the stack. The address for this identification is called CFA (reference frame address). Typically, the CFA will be the value of the stack pointer just before the function call.
So, * typically * is a point, but it's a rough understanding.
Well, as usual, DWARF compresses this table using a stack machine, but the actual work is done by pyelftools
. I can't see the end when I write down the details about the expansion method, so I will explain only the atmosphere of chasing the expanded table.
First, I tried to display the information of the LZ4_compress_fast ()
function recorded in the .debug_frame
section with ʻeu-readelf -w. The part with
Program` is the code of the stack machine.
[ 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 address encoding 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
The address register correspondence table will be restored from here, but when decoded with pyrlftools, it will be expanded to the following form.
# 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])
The table below summarizes this in the form of a table.
By the way, here, 16th, $ rip is assigned as the register return_address_register to store the return destination, but it may not be assigned to the actual register depending on the architecture.
address | 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) |
Let's actually use this table to restore the address of the caller who called 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
First, move to the stack frame where the LZ4_compress_fast ()
function is executing.
(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);
The execution address at this time is 0x404722, so if you search the above table by address,
address | CFA | r6 ($rbp) | r16 ($rip, return_address) |
---|---|---|---|
0x4046A7 | r6+16 | *(CFA-16) | *(CFA-8) |
This line matches. The location of the CFA is $ rbp + 16
because r6 == $ rbp
. And since r16
is CFA-8
, ** return_address ** is:
(gdb) x $rbp+16-8
0x7fffffffd568: 0x00404776
Should be. Since this address certainly matches the execution address of frame # 3, it means that the caller address can be restored.
There are some caveats here. I'm referring to r6 as the location of the CFA, which is now r6 at the time of 0x404722 execution, not the restored r6 for call recall. If not, it will circulate because it is based on CFA to restore r6.
In other words:
It is necessary to calculate in the order.
Based on the above, the following functions are processing. In addition to restoring the return_address and register, the stack is also rewound here.
def get_prev_frame(cu: CompileUnit,
addr: int,
regs: List[int],
read_mem: Callable[[int], int]) -> Tuple[Optional[int], Optional[List[int]]]:
"""
Execution address register and memory(Mainly stack)From, identify the stack frame,
Restore the caller's address and the register at that time
:param cu:CU information
:param addr:Execution address
:param regs:List of registers indexed by DWARF Register Number
:param read_mem:A function that reads memory. Read is a pointer(register)size
:return:Caller address and register
"""
if cu.dwarfinfo.has_CFI():
entries = cu.dwarfinfo.CFI_entries()
else:
# .debug_There is no frame
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:
#Restore the return address and its registers
cfa_rule = row["cfa"] # type: CFARule
assert cfa_rule.expr is None, "DWARF expression not supported"
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, "Not supported except 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, "Not supported except OFFSET"
prev_regs[key] = read_mem(cfa + reg.arg)
#Stack rewind
unwind_stack(prev_regs, cfa)
return read_mem(return_address), prev_regs
return None, None
Now that it's complete, let's run it. Place bt.py
in the current directory and try calling the python module from gdb. If you keep shifting the frame, the register value will change, so don't forget to return it to the first frame!
(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
It looks pretty good, but if you look closely, you'll notice that one frame is missing.
This is because the inline expansion has not been parsed properly. The LZ4_compress_generic ()
function is inlined in the LZ4_compress_fast_extState ()
function, but I'm decoding it as the LZ4_compress_fast_extState ()
function without noticing it.
In order to interpret inline, you have to take DW_TAG_inlined_subroutine
seriously, but there is not enough space to write it, so I will stop here.
Also, the parameters are not displayed. Again, this is a lot of work to do, such as looking at the .debug_loc
section, to write it (ry)
Kemono Friends, I wanted to see the continuation. .. ..
Recommended Posts