Blogs

检查堆栈以获取有趣和利润

史蒂夫布兰望2020年2月19日1条评论

好吧,可能不是那么多的利润,但肯定是为了好玩。这是一个漫游的探索和发现之旅,学习各种有趣和有用的东西。

嵌入式系统的关注之一是它需要多少内存,称为 记忆足迹。这包括程序所需的持久存储(即存储可执行图像的闪存或文件系统空间),并且在长时间运行时保持数据所需的易失性存储器(即其所有味道中的RAM) 。

RAM由3个区域组成: 静态记忆, , 和 。对于C语言:

  • 静态存储器是用于存储全局变量,文件范围静态变量和函数范围静态变量的内存的固定存储器。这些形成.bss和.data段。
  • 堆是通过分配的动态内存 Malloc() 并通过解放的透视 自由().
  • 堆栈是通过调用函数自动分配的动态内存,该函数由当前呼叫堆栈中的每个函数组成,包含功能局部变量和返回信息(即返回函数返回时的地址)。它也可以通过 Alloca() 函数,扩展当前堆栈帧。在任何一种情况下,堆叠函数返回时会自动解除堆栈。

虽然堆和堆栈都是动态使用的,而他们的分配池  本身被分配为已保留的固定静态存储区域。这些区域的大小由运行时环境定义,并且在某些情况下可以从其默认值调整,特别是在裸机上运行的嵌入式系统或在RTOS(实时操作系统下)。

涉及动态内存大小时,有两个竞争要求。首先,通常希望利用最小存储量运行,使得产品可以用较小的存储芯片运输,或内置于微控制器中的较小容量存储器,以最大限度地减少成本。

其次,该区域需要足够大以处理系统在长期内运行的最大动态分配,对于所有代码路径,无论它是什么。

当动态分配溢出时,可能会发生错误的事情,超过为它们保留的空间。系统可能会崩溃,或者它可能会继续运行,但有损坏的信息导致它行为不端。根据系统的控制,这可能具有严重的真实后果。一个不端行为的音乐播放器可能很烦人,但行为不端的发动机或工厂控制系统可以杀死人们。

这些条件被称为 堆疲惫堆栈溢出。为了避开它们,您需要知道系统需要多少堆和堆栈,因此嵌入式系统开发期间的共同任务是测量内存消耗(注意,在某些嵌入式系统中,由于风险,禁止使用堆的使用碎片,非确定性分配时间和耗尽)。

这些测量可以帮助驱动关于购买的大小筹码以及对软件进行更改的决策。对于芯片尺寸已经通过成本和硬件要求建立的情况,它们可以帮助驱动关于是否使用该软件的决策,或者找到具有更好测量的替代软件。

不同的系统具有不同的工具来进行测量。在这里,我正在使用raspberry pi运行Raspbian Linux。这些特定技术应适用于任何Linux平台。其他,非Linux系统应具有相似的功能,允许通常类似的技术。

在Linux上这样做的优势在于它是一个非常简单的工作平台。内置了各种好工具,您可以在同一设备上进行本机开发,开发和测试。与其他嵌入式系统环境并不总是如此,您必须进行交叉开发(即您正在进行开发的系统, 发展系统,与运行代码的系统完全不同, 目标系统)。

在Linux上这样做的缺点是Linux有自己的一组库和特定的做事方式,因此这些信息并不总是直接适用于其他系统。特别是,它是通用操作系统和运行时环境,而不是嵌入式系统。所以在努力在那些其他系统上工作时,请记住。

在这种特殊情况下,我发现系统在其其余操作中,系统在其初始化中使用了更大的堆栈。这是不幸的,因为这意味着它即使它可以长时间运行小堆栈,它需要一个较大的堆栈,以便短暂的时间,然后余下的时间浪费。有时你只需要忍受那个,但它值得挖掘理解,看看是否有任何你可以做的事情。

我发现的是 getaddrinfo() 用于设置Linux网络套接字连接的功能消耗了很多堆栈,尤其是当您认为它正在处理它可能是短主机名字符串字符串时。进一步调查,我意识到我无法做到这一点,并且覆盆子PI对堆栈有很多内存,但它是用于说明调试和分析技术的有用练习。

从一个简单的程序开始锻炼功能。我会跑下这个 GDB.,GNU调试器,要检查堆栈,使用一些GDB脚本来帮助。然后我将使用我发现的信息来检查 glibc. 源代码,GNU C运行时库。一路上,我们不仅仅是关于函数的内部和它所谓的内容,还要了解动态库加载在Linux下的工作原因。

关于GDB的完整文件是在 用GDB调试。但逐渐学习它最容易。 GDB命令可以缩写,因此,虽然我将使用一些完整命令,我将使用很多缩写。当我使用它时,我会简要解释每个命令。在提示符中击中返回重复或继续最后一个命令(我在下面的列表中添加了额外的空行以进行可读性,但如果您看到行上没有其他内容的提示,那就是我刚刚在重复返回的地方/继续效应)。 GDB是一个很棒的工具,非常值得学习,广泛支持一系列平台。在某些情况下,其他工具建立在其顶部。

这些技术中的许多技术也适用于裸金属嵌入式系统。您可以针对通过某种通信通道连接的远程目标设备运行GDB的跨工具版本。例如,我在通过USB电缆连接到我的笔记本电脑的St Micro Nucleo板上进行了类似的东西。我在笔记本电脑上运行Openocd,它在电缆上与内置的内置ST-Link通信。一起,Openocd和电缆为GDB的ARM跨工具版本提供通信通道,也在笔记本电脑上运行,可以执行远程目标的调试。我将在后来的博客文章中覆盖。

与核心一样,覆盆子PI处理器也是ARM芯片,因此它有助于了解ARM汇编语言和EABI(嵌入式应用程序二进制接口)进行调试。您不需要成为其中的专家,但您需要能够在粗略的水平读取它;任何以前的汇编语言的经验都有助于帮助。我发现 本教程 只是我需要的。

这是程序(test-getaddrinfo.co),足以运行被测功能并提供一些GDB断点目标(主要的() 甚至没有任何参数):

#include <sys/socket.h>
#include <netdb.h>
#include <string.h>

int
main()
{
    struct addrinfo  hints;
    struct addrinfo* address_list;
    
    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    
    int result = getaddrinfo("test.example.com", "80", &hints, &address_list);
    return result;
}

通过-g选项使用调试符号构建它:

[email protected]:~/Projects/test-getaddrinfo $ gcc test-getaddrinfo.c -o test-getaddrinfo -g

这是一组GDB脚本函数(Stack_functions.gdb),用于播放堆栈。 GDB脚本语言非常简单,非常值得了解;这是 关于它的文件.

# Functions for examining and manipulating the stack in gdb.

# Script constants.
set $one_kb        = 1024.0
set $safety_margin = 16

# Raspbian Linux stack parameters.
set $stack_start   = 0x7efdf000
set $stack_end     = 0x7f000000
set $stack_size    = $stack_end - $stack_start

define stack_args
    if $argc < 2
        printf "Usage: stack_args <offset|start> <length|end>\n"
    else
        if $arg0 < $stack_start
            # Assume arg0 is a relative offset from start of stack.
            set $offset = (int)$arg0
        else
            # Assume arg0 is an absolute address, so compute its offset.
            set $offset = (int)$arg0 - $stack_start
        end
        
        if $arg1 < $stack_start
            # Assume arg1 is a relative length.
            set $length = (int)$arg1
        else
            # Assume arg1 is an absolute address, so compute its length.
            set $length = (int)$arg1 - $stack_start - $offset
        end
    end
end

document stack_args
Usage: stack_args <offset|start> <length|end>

Set stack region offset and length from arguments.
end

define dump_stack
    if $argc < 2
        printf "Usage: dump_stack <offset|start> <length|end>\n"
    else
        stack_args $arg0 $arg1
        
        set $i = 0
        while $i < $length
            set $addr = $stack_start + $offset + $i
            x/4wx $addr
            set $i = $i + 16
        end
    end
end

document dump_stack
Usage: dump_stack <offset|start> <length|end>

Dumps stack starting at <offset|start> bytes, 4 longwords at a time,
for <length|end> bytes.
end

define clear_stack
    if $argc < 2
        printf "Usage: clear_stack <offset|start> <length|end>\n"
    else
        stack_args $arg0 $arg1
        
        if $stack_start + $offset + $safety_margin >= $sp
            printf "Error: start is in active stack.\n"
        else
            if $stack_start + $offset + $length + safety_margin >= $sp
                printf "Error: end is in active stack.\n"
            else
                set $i = 0
                while $i < $length
                    set $addr = $stack_start + $offset + $i
                    set *((int *) $addr) = 0
                    set $i = $i + 4
                    
                    # Takes a while, so give some feedback.
                    if $i % 10000 == 0
                        printf "Cleared %d\n", $i
                    end
                end
            end
        end
    end
end

document clear_stack
Usage: clear_stack <offset|start> <length|end>

Clears stack starting at <offset|start> bytes, one longword at a time,
for <length|end> bytes.
end

define stack_offset
    if $argc < 1
        printf "Usage: stack_offset <address>\n"
    else
        # Cast to int is needed to set $depth when $arg0 is $sp.
        set $addr   = (int)$arg0
        set $offset = $addr - $stack_start
        set $depth  = $stack_end - $addr
        
        printf "Address    %10d = 0x%08x\n", $addr, $addr
        
        if $addr < $stack_start || $addr >= $stack_end
            printf "Warning: address is not in stack.\n"
        end
        
        printf "Stack size   %6d = 0x%05x = %5.1fKB, 0x%x-0x%x\n", $stack_size, $stack_size, $stack_size / $one_kb, $stack_start, $stack_end
        printf "Stack offset %6d = 0x%05x = %5.1fKB\n", $offset, $offset, $offset / $one_kb
        printf "Stack depth  %6d = 0x%05x = %5.1fKB\n", $depth, $depth, $depth / $one_kb
    end
end

document stack_offset
Usage: stack_offset <address>

Shows stack offset and depth represented by address.
end

define scan_stack
    if $argc < 2
        printf "Usage: scan_stack <offset|start> <length|end>\n"
    else
        stack_args $arg0 $arg1
        
        set $addr = $stack_start + $offset
        set $i    = 0
        while $i < $length && *((int *) $addr) == 0
            set $addr = $stack_start + $offset + $i
            set $i = $i + 4
            
            # Takes a while, so give some feedback.
            if $i % 10000 == 0
                printf "Scanned %d\n", $i
            end
        end

        if *((int *) $addr) != 0
            if $addr < $sp
                set $offset = $sp - $addr
                printf "Found data %d bytes deeper than current stack frame (0x%x).\n", $offset, $sp
            else
                printf "Stack is clear up to current stack frame (0x%x), it is deepest stack usage.\n", $sp
            end
            
            stack_offset $addr
            dump_stack $addr-$stack_start 64
        else
            printf "Stack is clear in requested range.\n"
        end
    end
end

document scan_stack
Usage: scan_stack <offset|start> <length|end>

Scans stack for non-zero contents starting at <offset|start> bytes, one
longword at a time, for <length|end> bytes.
end

define stack_walk
    set $first_sp = $sp
    set $last_sp  = $sp
    set $total    = 0
    frame
    printf "Top stack frame 0x%08x\n\n", $last_sp
    
    # Loop will error out gracefully when there are no more frames.
    while 1
        up
        set $delta   = $sp - $last_sp
        set $total   = $total + $delta
        printf "Last stack frame 0x%08x, current 0x%08x, size of last %4d = 0x%03x, total deeper %6d = 0x%05x = %5.1fKB\n\n", $last_sp, $sp, $delta, $delta, $total, $total, $total / $one_kb
        set $last_sp = $sp
    end
end

document stack_walk
Usage: stack_walk

Walks stack frames upward from currently selected frame and computes
incremental and cumulative size of frames, so that stack consumption
can be attributed to specific functions.

Use "f 0" to select deepest frame of call stack, or "f <n>" to select
frame <n> higher up in stack.
end

如何知道堆栈边界的位置 $ stack_start.$ stack_end. 变量?在Linux上,文件 / proc /<pid>/maps 列出各种内存部分的地址(PROC文件系统实际上是一个伪系统,它用作内核数据的用户界面)。您可以Grep for“堆栈”以查看地址范围。此信息也可用于运行程序的GDB会话中 信息Proc Map. 命令。由于Linux使用虚拟内存,因此每个进程都使用相同的地址。

这种类型的东西是非常系统的,因此不同的Linux平台可能会使用不同的地址。对于诸如Nucleo板的裸金属系统,并且可能用于RTOS,这些地址将在链接控制脚本(.ld文件)中找到。

Linux.堆栈向后生长,从端开始(即从较高地址到较低地址)。堆栈的大小称为其深度。它由一系列系列组成 堆栈框架,每个函数调用一个呼叫树(想想一堆板构建,但使用框架而不是板)。每个帧都包含函数需要的所有临时存储。这包括保存需要保留呼叫的处理器寄存器以及任何局部变量。在某些情况下,函数参数可以通过堆栈传递,但ARM EABI指示函数通过寄存器传递第一组参数。

在进程创建时,堆栈被创建为零填充内存。它初始化为已知值的事实使得通过搜索第一个非零位置可以容易地找到最深的消耗点。

两个寄存器对于跟踪堆栈,SP(堆栈指针)和FP(帧指针)很重要。 FP实际上是R11。 GDB象征性地识别它们 $ sp.$r11.

将数据推到堆栈上并从中弹出数据,自动更改SP。也可以从SP中减去偏移值并添加到它以批量分配和解除空间。

以下是关于通过从SP减去分配的内存的大量重要说明:这不会更改分配空间中的内存位置的值。它只是将堆栈边界移动到包括它们,并且存储器具有先前在那里存储的任何值。因此,变量或数据结构该空间映射到程序中是 未初始化。这就是为什么你必须在阅读之前以某种方式分配给本地变量的值。否则,您读取随机,未知的数据留在那里留给那些位置。这是一个常见的错误来源。

在函数中的某些点处,SP将保存到FP以标记帧。完成如何以及何时完成的实际机制由EABI指定。

要分析堆栈使用情况,请在GDB下启动程序并在堆栈功能中拉动(此处-Q选项是安静模式,以抑制Boilerplate启动消息):

[email protected]:~/Projects/test-getaddrinfo $ gdb -q ./test-getaddrinfo 
Reading symbols from ./test-getaddrinfo...done.

(gdb) source stack_functions.gdb

该程序实际上并未运行。列出程序源并设置断点 主要的() 功能和包含最终的线 返回 statement:

(gdb) list
1 #include <sys/socket.h>
2 #include <netdb.h>
3 #include <string.h>
4 
5 int
6 main()
7 {
8     struct addrinfo  hints;
9     struct addrinfo* address_list;
10 
(gdb) 
11     memset(&hints, 0, sizeof(hints));
12     hints.ai_family   = AF_UNSPEC;
13     hints.ai_socktype = SOCK_STREAM;
14     hints.ai_protocol = IPPROTO_TCP;
15 
16     int result = getaddrinfo("test.example.com", "80", &hints, &address_list);
17     return result;
18 }

(gdb) b main
Breakpoint 1 at 0x10480: file test-getaddrinfo.c, line 11.

(gdb) b 17
Breakpoint 2 at 0x104c4: file test-getaddrinfo.c, line 17.

运行程序。当它停止在第一个断点时,第一个可执行线 主要的() 函数,显示进程内存映射以验证堆栈地址(在命令输出中查找[堆栈]行):

(gdb) r
Starting program: /home/pi/Projects/test-getaddrinfo/test-getaddrinfo 

Breakpoint 1, main () at test-getaddrinfo.c:11
11     memset(&hints, 0, sizeof(hints));

(gdb) info proc map
process 10163
Mapped address spaces:
 Start Addr   End Addr       Size     Offset objfile
    0x10000    0x11000     0x1000        0x0 /home/pi/Projects/test-getaddrinfo/test-getaddrinfo
    0x20000    0x21000     0x1000        0x0 /home/pi/Projects/test-getaddrinfo/test-getaddrinfo
    0x21000    0x22000     0x1000     0x1000 /home/pi/Projects/test-getaddrinfo/test-getaddrinfo
 0x76e64000 0x76f8e000   0x12a000        0x0 /lib/arm-linux-gnueabihf/libc-2.24.so
 0x76f8e000 0x76f9d000     0xf000   0x12a000 /lib/arm-linux-gnueabihf/libc-2.24.so
 0x76f9d000 0x76f9f000     0x2000   0x129000 /lib/arm-linux-gnueabihf/libc-2.24.so
 0x76f9f000 0x76fa0000     0x1000   0x12b000 /lib/arm-linux-gnueabihf/libc-2.24.so
 0x76fa0000 0x76fa3000     0x3000        0x0 
 0x76fb8000 0x76fbd000     0x5000        0x0 /usr/lib/arm-linux-gnueabihf/libarmmem.so
 0x76fbd000 0x76fcc000     0xf000     0x5000 /usr/lib/arm-linux-gnueabihf/libarmmem.so
 0x76fcc000 0x76fcd000     0x1000     0x4000 /usr/lib/arm-linux-gnueabihf/libarmmem.so
 0x76fcd000 0x76fce000     0x1000     0x5000 /usr/lib/arm-linux-gnueabihf/libarmmem.so
 0x76fce000 0x76fef000    0x21000        0x0 /lib/arm-linux-gnueabihf/ld-2.24.so
 0x76ff9000 0x76ffb000     0x2000        0x0 
 0x76ffb000 0x76ffc000     0x1000        0x0 [sigpage]
 0x76ffc000 0x76ffd000     0x1000        0x0 [vvar]
 0x76ffd000 0x76ffe000     0x1000        0x0 [vdso]
 0x76ffe000 0x76fff000     0x1000    0x20000 /lib/arm-linux-gnueabihf/ld-2.24.so
 0x76fff000 0x77000000     0x1000    0x21000 /lib/arm-linux-gnueabihf/ld-2.24.so
 0x7efdf000 0x7f000000    0x21000        0x0 [stack]
 0xffff0000 0xffff1000     0x1000        0x0 [vectors]

那些是我在堆栈函数中使用的地址。从其零偏移扫描堆栈的全长以找到第一个非零值(堆栈函数接受堆栈的偏移值或绝对内存地址):

(gdb) scan_stack 0 $stack_size
Scanned 10000
Scanned 20000
Scanned 30000
Scanned 40000
Scanned 50000
Scanned 60000
Scanned 70000
Scanned 80000
Scanned 90000
Scanned 100000
Scanned 110000
Scanned 120000
Found data 4660 bytes deeper than current stack frame (0x7effeeb0).
Address    2130697340 = 0x7effdc7c
Stack size   135168 = 0x21000 = 132.0KB, 0x7efdf000-0x7f000000
Stack offset 126076 = 0x1ec7c = 123.1KB
Stack depth    9092 = 0x02384 =   8.9KB
0x7effdc7c: 0x00000020 0x00002e41 0x61656100 0x01006962
0x7effdc8c: 0x00000024 0x06003605 0x09010806 0x12020a01
0x7effdc9c: 0x14011304 0x16011501 0x18031701 0x1c021a01
0x7effdcac: 0x00012201 0x00000000 0x7effe8f4 0x00000000

扫描在比当前堆栈帧更深入地进入堆叠,扫描在4660字节处找到非零32位字。它打印出堆栈大小和地址,单词的偏移量和偏移代表的总深度。然后它转储该内存的内容16个单词。请注意,其中一个单词包含自身是堆栈地址的值(即堆栈地址0x7efdf000-0x7f000000的范围)。

当程序甚至没有开始时,您可能想知道为什么堆栈上的东西比当前帧更深入 主要的() 功能尚未。那是 预先 代码在工作。每个系统都有一些在实际程序代码之前运行的启动代码。它可以执行库初始化,数据初始化(例如,将来自可执行图像的.data段的内容复制到初始化的全局和静态变量留出静态内存空间),设置寄存器等。非常系统依赖。

那么当前的堆栈帧在哪里?查看回溯,这是呼叫树中所有函数的堆栈帧列表,直到断点,查看当前SP和FP表示的堆叠偏移:

(gdb) ba
#0  main () at test-getaddrinfo.c:11

(gdb) stack_offset $sp
Address    2130702000 = 0x7effeeb0
Stack size   135168 = 0x21000 = 132.0KB, 0x7efdf000-0x7f000000
Stack offset 130736 = 0x1feb0 = 127.7KB
Stack depth    4432 = 0x01150 =   4.3KB

(gdb) stack_offset $r11
Address    2130702044 = 0x7effeedc
Stack size   135168 = 0x21000 = 132.0KB, 0x7efdf000-0x7f000000
Stack offset 130780 = 0x1fedc = 127.7KB
Stack depth    4388 = 0x01124 =   4.3KB

答案真的很短,只是 主要的(),没有迹象表明谁打电话(虽然我们知道在主代码中的某些东西让我们这么远)。 SP和FP彼此略有不同,指示堆栈帧包含少量数据。但一般来说,总堆叠深度为4.3kb,包括 主要的() 堆栈框架,以及任何预先安装。

查看编译器生成的实际汇编代码 主要的()。由于它存在于内存中的二进制机器代码,我们需要将其拆卸回汇编语言(GDB不支持分解汇编语言回到原始C源代码,但它确实知道哪条源代码行代表了哪些组件范围的源代码通过GCC -G选项包含的调试符号指令):

(gdb) disassemble 
Dump of assembler code for function main:
   0x00010474 <+0>: push {r11, lr}
   0x00010478 <+4>: add r11, sp, #4
   0x0001047c <+8>: sub sp, sp, #40 ; 0x28
=> 0x00010480 <+12>: sub r3, r11, #40 ; 0x28
   0x00010484 <+16>: mov r2, #32
   0x00010488 <+20>: mov r1, #0
   0x0001048c <+24>: mov r0, r3
   0x00010490 <+28>: bl 0x10328 <[email protected]>
   0x00010494 <+32>: mov r3, #0
   0x00010498 <+36>: str r3, [r11, #-36] ; 0xffffffdc
   0x0001049c <+40>: mov r3, #1
   0x000104a0 <+44>: str r3, [r11, #-32] ; 0xffffffe0
   0x000104a4 <+48>: mov r3, #6
   0x000104a8 <+52>: str r3, [r11, #-28] ; 0xffffffe4
   0x000104ac <+56>: sub r3, r11, #44 ; 0x2c
   0x000104b0 <+60>: sub r2, r11, #40 ; 0x28
   0x000104b4 <+64>: ldr r1, [pc, #24] ; 0x104d4 <main+96>
   0x000104b8 <+68>: ldr r0, [pc, #24] ; 0x104d8 <main+100>
   0x000104bc <+72>: bl 0x10334 <[email protected]>
   0x000104c0 <+76>: str r0, [r11, #-8]
   0x000104c4 <+80>: ldr r3, [r11, #-8]
   0x000104c8 <+84>: mov r0, r3
   0x000104cc <+88>: sub sp, r11, #4
   0x000104d0 <+92>: pop {r11, pc}
   0x000104d4 <+96>: andeq r0, r1, r12, asr #10
   0x000104d8 <+100>: andeq r0, r1, r0, asr r5
End of assembler dump.

每个功能都包括序幕,身体和外表。根据EABI,PROLOGUE和HILOGUE是编译器生成的自动代码,即编译器生成进入和退出功能。 MODIO是编译器生成的代码,根据C语言语句来生成函数的逻辑。

这些特定细节基于分类函数调用的比特变化,但通常:

  • 序幕保存关闭需要保留的寄存器,同时在函数重用并为局部变量设置空间。
  • ePilogue设置函数返回值,还原保存的寄存器,并删除本地变量。

当源代码可用于GDB时,Disssemble命令将选项/ s选为显示源和程序集。这使得更容易看到功能的不同部分,并且是一种有用的方法,了解编译器如何将C源构造转换为组装;优化的代码变得更加有趣。

(gdb) disassemble /s
Dump of assembler code for function main:
test-getaddrinfo.c:
7 {
   0x00010474 <+0>: push {r11, lr}
   0x00010478 <+4>: add r11, sp, #4
   0x0001047c <+8>: sub sp, sp, #40 ; 0x28
8     struct addrinfo  hints;
9     struct addrinfo* address_list;

10 
11     memset(&hints, 0, sizeof(hints));
   0x00010480 <+12>: sub r3, r11, #40 ; 0x28
   0x00010484 <+16>: mov r2, #32
   0x00010488 <+20>: mov r1, #0
   0x0001048c <+24>: mov r0, r3
   0x00010490 <+28>: bl 0x10328 <[email protected]>

12     hints.ai_family   = AF_UNSPEC;
   0x00010494 <+32>: mov r3, #0
   0x00010498 <+36>: str r3, [r11, #-36] ; 0xffffffdc

13     hints.ai_socktype = SOCK_STREAM;
   0x0001049c <+40>: mov r3, #1
   0x000104a0 <+44>: str r3, [r11, #-32] ; 0xffffffe0

14     hints.ai_protocol = IPPROTO_TCP;
   0x000104a4 <+48>: mov r3, #6
   0x000104a8 <+52>: str r3, [r11, #-28] ; 0xffffffe4

15 
16     int result = getaddrinfo("test.example.com", "80", &hints, &address_list);
   0x000104ac <+56>: sub r3, r11, #44 ; 0x2c
   0x000104b0 <+60>: sub r2, r11, #40 ; 0x28
   0x000104b4 <+64>: ldr r1, [pc, #24] ; 0x104d4 <main+96>
   0x000104b8 <+68>: ldr r0, [pc, #24] ; 0x104d8 <main+100>
   0x000104bc <+72>: bl 0x10334 <[email protected]>
=> 0x000104c0 <+76>: str r0, [r11, #-8]

17     return result;
   0x000104c4 <+80>: ldr r3, [r11, #-8]

18 }
   0x000104c8 <+84>: mov r0, r3
   0x000104cc <+88>: sub sp, r11, #4
   0x000104d0 <+92>: pop {r11, pc}
   0x000104d4 <+96>: andeq r0, r1, r12, asr #10
   0x000104d8 <+100>: andeq r0, r1, r0, asr r5
End of assembler dump.

这是序言:

   0x00010474 <+0>: push {r11, lr}
   0x00010478 <+4>: add r11, sp, #4
   0x0001047c <+8>: sub sp, sp, #40 ; 0x28

这将FP和LR(链接寄存器)推向堆栈上的呼叫者的返回地址,然后将4个字节添加到SP,存储在FP中的结果,以在堆栈帧中分配保存的寄存器空间。然后它从SP中减去40个字节,以使局部变量。

这是外交:

   0x000104c8 <+84>: mov r0, r3
   0x000104cc <+88>: sub sp, r11, #4
   0x000104d0 <+92>: pop {r11, pc}
   0x000104d4 <+96>: andeq r0, r1, r12, asr #10
   0x000104d8 <+100>: andeq r0, r1, r0, asr r5

这将在R0中设置eABI指定的返回值,从FP中减去4个字节并将结果存储在SP中,然后弹出保存的FP和LR(我现在不确定那些andeq线路的情况,是那些特别的东西从main()?)。

但是这是一些微妙的。那些从序幕中的SP中减去的40个字节呢?为什么LR弹回电脑?

FP实际上包含SP的值 减去40个字节。标有框架的边界。因此,只需减去4,就足以将SP恢复到其先前的值,指向已保存的FP和LR。

通过将保存的LR直接弹出到PC中,处理器会自动在该保存地址执行执行。它相当于将保存的值突出到LR中,然后跳转到LR中的地址。

这些是编译器所做的有效代码的种类。但请注意,这是 未优化的代码。也就是说,它是直接实现C语句的代码。它并不总是很明显,装配说明是直接实现,因为有时,实现在进行寄存器中生成副作用的有趣事物,然后在后续语句中使用。

但是编译器有更多的伎俩它是袖子。您可以针对速度或空间进行优化,从而使其以略微不同的方式实现C代码。始终有多种方式实现了各种权衡。优化影响这些权衡,以实现特定目标,同时仍然遵循C源代码逻辑。

默认情况下,GCC生成未优化的代码,因为它允许您直接在GDB中跟踪执行。优化的代码可以从调试的角度来看奇怪的东西,使调试更加困难。它仍然是很可能的,它更复杂。

看看函数的主体,序幕与外交之间的代码。注意箭头指向偏移+12的指令。这就是当前断点持有执行的地方。看看BL说明:

...
   0x00010490 <+28>: bl 0x10328 <[email protected]>
...
   0x000104bc <+72>: bl 0x10334 <[email protected]

那些是函数调用 主要的() 制作。这些是分支和链接指令:分支到命名函数,并将当前的PC值链接(即,在LR中保存)作为返回地址。

函数名称匹配C源代码。但是这是什么 @plt. 东西?那是 过程链接表,图书馆加载的一部分。我们将在一下看Plt蹦床。你没有意识到这个包括体操,你呢?

但是,现在我们知道它与之相关的堆栈 主要的()。继续直到下一个断点,就在之前 主要的() exits:

(gdb) c
Continuing.

Breakpoint 2, main () at test-getaddrinfo.c:17
17     return result;

这意味着该计划已称为 Memset()getaddrinfo() 函数,做他们需要的任何堆栈操作,并返回这一行,在哪里 主要的() 即将返回其结果,结束该程序。

堆栈现在看起来像什么?

(gdb) stack_offset $sp
Address    2130702000 = 0x7effeeb0
Stack size   135168 = 0x21000 = 132.0KB, 0x7efdf000-0x7f000000
Stack offset 130736 = 0x1feb0 = 127.7KB
Stack depth    4432 = 0x01150 =   4.3KB

(gdb) stack_offset $r11
Address    2130702044 = 0x7effeedc
Stack size   135168 = 0x21000 = 132.0KB, 0x7efdf000-0x7f000000
Stack offset 130780 = 0x1fedc = 127.7KB
Stack depth    4388 = 0x01124 =   4.3KB

SP和FP看起来像之前一样。确认堆栈发生了什么,它返回到状态 主要的() 期待;它保持了背景 主要的()。扫描节目是什么?

(gdb) scan_stack 0 $stack_size
Scanned 10000
Scanned 20000
Scanned 30000
Scanned 40000
Scanned 50000
Scanned 60000
Scanned 70000
Scanned 80000
Scanned 90000
Scanned 100000
Scanned 110000
Found data 11648 bytes deeper than current stack frame (0x7effeeb0).
Address    2130690352 = 0x7effc130
Stack size   135168 = 0x21000 = 132.0KB, 0x7efdf000-0x7f000000
Stack offset 119088 = 0x1d130 = 116.3KB
Stack depth   16080 = 0x03ed0 =  15.7KB
0x7effc130: 0x76ff94b0 0x7effc1a8 0x76e66c28 0x000004b0
0x7effc140: 0x7effc1ac 0x76fd8548 0x00000001 0x76e6c754
0x7effc150: 0x000004b0 0x76e70804 0x76ff94b0 0x7effc1ac
0x7effc160: 0x7effc1a8 0x00000000 0x76ffecf0 0x76e70804

一些东西深入到堆栈中。它有11,648个字节的数据,最大深度为15.7kb。

我们如何找出谁做了这件事?答案是设置一个 观察点。这基本上是一个 数据 断点。现有的断点是 执行 断点,其中GDB在执行特定地址时中断程序。对于数据断点,即观察点,GDB在写入特定地址时中断程序;它看着看待地址何时写入。

重新启动程序。当它打破 主要的(),在堆栈地址上设置扫描的堆栈地址中的观察点,将其作为指向int的指针(使用观察点的指针可能有点Finicky,因此请确保这些步骤恰好是此顺序,具有此精确语法):

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/pi/Projects/test-getaddrinfo/test-getaddrinfo 

Breakpoint 1, main () at test-getaddrinfo.c:11
11     memset(&hints, 0, sizeof(hints));

(gdb) watch *(int*)0x7effc130
Hardware watchpoint 3: *(int*)0x7effc130

从那里继续,而Bam!抓住了作者的行为:

(gdb) c
Continuing.

Hardware watchpoint 3: *(int*)0x7effc130

Old value = 0
New value = 1996461232
check_match ([email protected]=0x76df8116 "strcasecmp", ref=0x76df775c, [email protected]=0x59c2869, version=0x22e80, [email protected]=0x76fffabc, flags=1, [email protected]=2, [email protected]=1, sym=0x76e6c754, 
    [email protected]=0x770037f0, [email protected]=1200, strtab=0x76e70804 "", [email protected]=0x0, [email protected]=0x76ff94b0, [email protected]=0x7effc1ac, [email protected]=0x7effc1a8) at dl-lookup.c:92
92 dl-lookup.c: No such file or directory.

这个方案在这一点是什么?回溯将显示。它比以前的回溯更长,一个深呼的呼叫堆栈:

(gdb) ba
#0  check_match ([email protected]=0x76df8116 "strcasecmp", ref=0x76df775c, [email protected]=0x59c2869, version=0x22e80, [email protected]=0x76fffabc, flags=1, [email protected]=2, [email protected]=1, sym=0x76e6c754, 
    [email protected]=0x770037f0, [email protected]=1200, strtab=0x76e70804 "", [email protected]=0x0, [email protected]=0x76ff94b0, [email protected]=0x7effc1ac, [email protected]=0x7effc1a8) at dl-lookup.c:92
#1  0x76fd8548 in do_lookup_x (undef_name=0xb3850d3a <error: Cannot access memory at address 0xb3850d3a>, [email protected]=0x76df8116 "strcasecmp", new_hash=1994852356, [email protected]=3011841338, old_hash=0x76fec84c, 
    [email protected]=0x7effc218, ref=0x59c2869, result=<optimized out>, [email protected]=0x7effc220, scope=0x76fffabc, i=<optimized out>, version=<optimized out>, [email protected]=0x22e80, [email protected]=1, [email protected]=0x0, 
    [email protected]=1, [email protected]=0x22ac0) at dl-lookup.c:423
#2  0x76fd8b20 in _dl_lookup_symbol_x (undef_name=0x76df8116 "strcasecmp", undef_map=0x22ac0, ref=0x7effc28c, [email protected]=0x7effc284, symbol_scope=0x22c78, version=0x22e80, [email protected]=1, flags=1, 
    [email protected]=0x0) at dl-lookup.c:833
#3  0x76fde10c in _dl_fixup (l=<optimized out>, reloc_arg=<optimized out>) at dl-runtime.c:111
#4  0x76fe5320 in _dl_runtime_resolve () at ../sysdeps/arm/dl-trampoline.S:57
#5  0x76e05eec in __GI_ns_samename ([email protected]=0x7effcaf8 "test.example.com", b=0x7effcf38 "test.example.com", [email protected]=0x402 <error: Cannot access memory at address 0x402>) at ns_samedomain.c:196
#6  0x76dff850 in __GI___res_nameinquery (name=0x402 <error: Cannot access memory at address 0x402>, [email protected]=0x0, type=1, class=1, [email protected]=0x7effe088 "~\027\201\203", 
    [email protected]=0x7effe888 "_nss_dns_get\210\340\377~") at res_send.c:287
#7  0x76dff984 in __GI___res_queriesmatch (buf1=0x7effd4e0 "~\027\001", [email protected]=0x7effd40c "n", eom1=0x7effd502 "", [email protected]=0x7effd40c "n", buf2=0x7effe088 "~\027\201\203", eom2=0x7effe888 "_nss_dns_get\210\340\377~")
    at res_send.c:342
#8  0x76e00578 in send_dg (ansp2_malloced=<optimized out>, resplen2=<optimized out>, anssizp2=<optimized out>, ansp2=<optimized out>, anscp=<optimized out>, gotsomewhere=<synthetic pointer>, v_circuit=<synthetic pointer>, 
    ns=0, terrno=0x7effe088, anssizp=0x7effd4c0, ansp=0x7effd3fc, buflen2=<optimized out>, buf2=<optimized out>, buflen=<optimized out>, buf=<optimized out>, statp=0x7effd420) at res_send.c:1422
#9  __libc_res_nsend ([email protected]=0x76fa1b50 <_res>, buf=0x7effd40c "n", [email protected]=0x7effd4e0 "~\027\001", buflen=0, [email protected]=34, buf2=0x0, [email protected]=0x7effd504 "s\372\001", [email protected]=34, 
    ans=<optimized out>, [email protected]=0x7effe088 "~\027\201\203", anssiz=<optimized out>, [email protected]=2048, [email protected]=0x7effe894, [email protected]=0x7effe898, [email protected]=0x7effe89c, 
    [email protected]=0x7effe8a0, [email protected]=0x7effe8a4) at res_send.c:533
#10 0x76dfdd70 in __GI___libc_res_nquery ([email protected]=0x76fa1b50 <_res>, name=0x10550 "test.example.com", [email protected]=1, type=439963904, [email protected]=0, answer=0x7effe088 "~\027\201\203", [email protected]=0x0, 
    anslen=2048, [email protected]=439963904, answerp=0x7effe894, [email protected]=0x76ffece8 <__stack_chk_guard>, [email protected]=0x7effe898, [email protected]=0x7effe89c, [email protected]=0x7effe8a0, 
    [email protected]=0x7effe8a4) at res_query.c:222
#11 0x76dfe37c in __libc_res_nquerydomain ([email protected]=0x76fa1b50 <_res>, name=0x7effe088 "~\027\201\203", [email protected]=0x10550 "test.example.com", [email protected]=0x0, class=1, [email protected]=0, type=439963904, 
    [email protected]=2130700444, answer=0x7effe088 "~\027\201\203", [email protected]=0x9d <error: Cannot access memory at address 0x9d>, anslen=2048, [email protected]=1994515264, [email protected]=0x7effe894, 
    [email protected]=0x7effe898, nanswerp2=0x7effe89c, [email protected]=0x7effe8a4, [email protected]=0x7effe8a0, [email protected]=0x7effe8a4) at res_query.c:592
#12 0x76dfe764 in __GI___libc_res_nsearch (statp=0x76fa1b50 <_res>, name=0x10550 "test.example.com", class=0, [email protected]=1, type=2130700444, [email protected]=439963904, answer=0x7effe088 "~\027\201\203", [email protected]=2048, 
    [email protected]=0x7effe894, [email protected]=0x7effe898, [email protected]=0x7effe89c, [email protected]=0x7effe8a0, [email protected]=0x7effe8a4) at res_query.c:376
#13 0x76e1e340 in _nss_dns_gethostbyname4_r ([email protected]=0x10550 "test.example.com", [email protected]=0x7effe998, buffer=0x7effea88 "\177", buflen=1024, [email protected]=0x7effe99c, [email protected]=0x7effe9ac, 
    [email protected]=0x0) at nss_dns/dns-host.c:326
#14 0x76f1dee0 in gaih_inet (name=<optimized out>, [email protected]=0x10550 "test.example.com", service=<optimized out>, req=0x7effeeb4, [email protected]=0x7effea40, naddrs=<optimized out>, [email protected]=0x7effea4c, 
    tmpbuf=<optimized out>, [email protected]=0x7effea80) at ../sysdeps/posix/getaddrinfo.c:848
#15 0x76f1f010 in __GI_getaddrinfo (name=<optimized out>, service=<optimized out>, hints=<optimized out>, pai=0x7effeeb0) at ../sysdeps/posix/getaddrinfo.c:2391
#16 0x000104c0 in main () at test-getaddrinfo.c:16

看着当前SP代表的堆栈偏移量,我们可以看到这确实是深点:

(gdb) stack_offset $sp
Address    2130690352 = 0x7effc130
Stack size   135168 = 0x21000 = 132.0KB, 0x7efdf000-0x7f000000
Stack offset 119088 = 0x1d130 = 116.3KB
Stack depth   16080 = 0x03ed0 =  15.7KB

步行堆栈,展开堆栈框架并计算每个堆栈的大小(Set Heigh命令禁用分页,因此GDB不会提示继续遍在输出中):

(gdb) set height 0
(gdb) stack_walk
#0  check_match ([email protected]=0x76df8116 "strcasecmp", ref=0x76df775c, [email protected]=0x59c2869, version=0x22e80, [email protected]=0x76fffabc, flags=1, [email protected]=2, [email protected]=1, sym=0x76e6c754, 
    [email protected]=0x770037f0, [email protected]=1200, strtab=0x76e70804 "", [email protected]=0x0, [email protected]=0x76ff94b0, [email protected]=0x7effc1ac, [email protected]=0x7effc1a8) at dl-lookup.c:92
92 in dl-lookup.c
Top stack frame 0x7effc130

#1  0x76fd8548 in do_lookup_x (undef_name=0xb3850d3a <error: Cannot access memory at address 0xb3850d3a>, [email protected]=0x76df8116 "strcasecmp", new_hash=1994852356, [email protected]=3011841338, old_hash=0x76fec84c, 
    [email protected]=0x7effc218, ref=0x59c2869, result=<optimized out>, [email protected]=0x7effc220, scope=0x76fffabc, i=<optimized out>, version=<optimized out>, [email protected]=0x22e80, [email protected]=1, [email protected]=0x0, 
    [email protected]=1, [email protected]=0x22ac0) at dl-lookup.c:423
423 in dl-lookup.c
Last stack frame 0x7effc130, current 0x7effc148, size of last   24 = 0x018, total deeper     24 = 0x00018 =   0.0KB

#2  0x76fd8b20 in _dl_lookup_symbol_x (undef_name=0x76df8116 "strcasecmp", undef_map=0x22ac0, ref=0x7effc28c, [email protected]=0x7effc284, symbol_scope=0x22c78, version=0x22e80, [email protected]=1, flags=1, 
    [email protected]=0x0) at dl-lookup.c:833
833 in dl-lookup.c
Last stack frame 0x7effc148, current 0x7effc1d8, size of last  144 = 0x090, total deeper    168 = 0x000a8 =   0.2KB

#3  0x76fde10c in _dl_fixup (l=<optimized out>, reloc_arg=<optimized out>) at dl-runtime.c:111
111 dl-runtime.c: No such file or directory.
Last stack frame 0x7effc1d8, current 0x7effc278, size of last  160 = 0x0a0, total deeper    328 = 0x00148 =   0.3KB

#4  0x76fe5320 in _dl_runtime_resolve () at ../sysdeps/arm/dl-trampoline.S:57
57 ../sysdeps/arm/dl-trampoline.S: No such file or directory.
Last stack frame 0x7effc278, current 0x7effc2a8, size of last   48 = 0x030, total deeper    376 = 0x00178 =   0.4KB

#5  0x76e05eec in __GI_ns_samename ([email protected]=0x7effcaf8 "test.example.com", b=0x7effcf38 "test.example.com", [email protected]=0x402 <error: Cannot access memory at address 0x402>) at ns_samedomain.c:196
196 ns_samedomain.c: No such file or directory.
Last stack frame 0x7effc2a8, current 0x7effc2c0, size of last   24 = 0x018, total deeper    400 = 0x00190 =   0.4KB

#6  0x76dff850 in __GI___res_nameinquery (name=0x402 <error: Cannot access memory at address 0x402>, [email protected]=0x0, type=1, class=1, [email protected]=0x7effe088 "~\027\201\203", 
    [email protected]=0x7effe888 "_nss_dns_get\210\340\377~") at res_send.c:287
287 res_send.c: No such file or directory.
Last stack frame 0x7effc2c0, current 0x7effcae8, size of last 2088 = 0x828, total deeper   2488 = 0x009b8 =   2.4KB

#7  0x76dff984 in __GI___res_queriesmatch (buf1=0x7effd4e0 "~\027\001", [email protected]=0x7effd40c "n", eom1=0x7effd502 "", [email protected]=0x7effd40c "n", buf2=0x7effe088 "~\027\201\203", eom2=0x7effe888 "_nss_dns_get\210\340\377~")
    at res_send.c:342
342 in res_send.c
Last stack frame 0x7effcae8, current 0x7effcf28, size of last 1088 = 0x440, total deeper   3576 = 0x00df8 =   3.5KB

#8  0x76e00578 in send_dg (ansp2_malloced=<optimized out>, resplen2=<optimized out>, anssizp2=<optimized out>, ansp2=<optimized out>, anscp=<optimized out>, gotsomewhere=<synthetic pointer>, v_circuit=<synthetic pointer>, 
    ns=0, terrno=0x7effe088, anssizp=0x7effd4c0, ansp=0x7effd3fc, buflen2=<optimized out>, buf2=<optimized out>, buflen=<optimized out>, buf=<optimized out>, statp=0x7effd420) at res_send.c:1422
1422 in res_send.c
Last stack frame 0x7effcf28, current 0x7effd368, size of last 1088 = 0x440, total deeper   4664 = 0x01238 =   4.6KB

#9  __libc_res_nsend ([email protected]=0x76fa1b50 <_res>, buf=0x7effd40c "n", [email protected]=0x7effd4e0 "~\027\001", buflen=0, [email protected]=34, buf2=0x0, [email protected]=0x7effd504 "s\372\001", [email protected]=34, 
    ans=<optimized out>, [email protected]=0x7effe088 "~\027\201\203", anssiz=<optimized out>, [email protected]=2048, [email protected]=0x7effe894, [email protected]=0x7effe898, [email protected]=0x7effe89c, 
    [email protected]=0x7effe8a0, [email protected]=0x7effe8a4) at res_send.c:533
533 in res_send.c
Last stack frame 0x7effd368, current 0x7effd368, size of last    0 = 0x000, total deeper   4664 = 0x01238 =   4.6KB

#10 0x76dfdd70 in __GI___libc_res_nquery ([email protected]try=0x76fa1b50 <_res>, name=0x10550 "test.example.com", [email protected]=1, type=439963904, [email protected]=0, answer=0x7effe088 "~\027\201\203", [email protected]=0x0, 
    anslen=2048, [email protected]=439963904, answerp=0x7effe894, [email protected]=0x76ffece8 <__stack_chk_guard>, [email protected]=0x7effe898, [email protected]=0x7effe89c, [email protected]=0x7effe8a0, 
    [email protected]=0x7effe8a4) at res_query.c:222
222 res_query.c: No such file or directory.
Last stack frame 0x7effd368, current 0x7effd4c0, size of last  344 = 0x158, total deeper   5008 = 0x01390 =   4.9KB

#11 0x76dfe37c in __libc_res_nquerydomain ([email protected]=0x76fa1b50 <_res>, name=0x7effe088 "~\027\201\203", [email protected]=0x10550 "test.example.com", [email protected]=0x0, class=1, [email protected]=0, type=439963904, 
    [email protected]=2130700444, answer=0x7effe088 "~\027\201\203", [email protected]=0x9d <error: Cannot access memory at address 0x9d>, anslen=2048, [email protected]=1994515264, [email protected]=0x7effe894, 
    [email protected]=0x7effe898, nanswerp2=0x7effe89c, [email protected]=0x7effe8a4, [email protected]=0x7effe8a0, [email protected]=0x7effe8a4) at res_query.c:592
592 in res_query.c
Last stack frame 0x7effd4c0, current 0x7effd780, size of last  704 = 0x2c0, total deeper   5712 = 0x01650 =   5.6KB

#12 0x76dfe764 in __GI___libc_res_nsearch (statp=0x76fa1b50 <_res>, name=0x10550 "test.example.com", class=0, [email protected]=1, type=2130700444, [email protected]=439963904, answer=0x7effe088 "~\027\201\203", [email protected]=2048, 
    [email protected]=0x7effe894, [email protected]=0x7effe898, [email protected]=0x7effe89c, [email protected]=0x7effe8a0, [email protected]=0x7effe8a4) at res_query.c:376
376 in res_query.c
Last stack frame 0x7effd780, current 0x7effdbe0, size of last 1120 = 0x460, total deeper   6832 = 0x01ab0 =   6.7KB

#13 0x76e1e340 in _nss_dns_gethostbyname4_r ([email protected]=0x10550 "test.example.com", [email protected]=0x7effe998, buffer=0x7effea88 "\177", buflen=1024, [email protected]=0x7effe99c, [email protected]=0x7effe9ac, 
    [email protected]=0x0) at nss_dns/dns-host.c:326
326 nss_dns/dns-host.c: No such file or directory.
Last stack frame 0x7effdbe0, current 0x7effe068, size of last 1160 = 0x488, total deeper   7992 = 0x01f38 =   7.8KB

#14 0x76f1dee0 in gaih_inet (name=<optimized out>, [email protected]=0x10550 "test.example.com", service=<optimized out>, req=0x7effeeb4, [email protected]=0x7effea40, naddrs=<optimized out>, [email protected]=0x7effea4c, 
    tmpbuf=<optimized out>, [email protected]=0x7effea80) at ../sysdeps/posix/getaddrinfo.c:848
848 ../sysdeps/posix/getaddrinfo.c: No such file or directory.
Last stack frame 0x7effe068, current 0x7effe8e0, size of last 2168 = 0x878, total deeper  10160 = 0x027b0 =   9.9KB

#15 0x76f1f010 in __GI_getaddrinfo (name=<optimized out>, service=<optimized out>, hints=<optimized out>, pai=0x7effeeb0) at ../sysdeps/posix/getaddrinfo.c:2391
2391 in ../sysdeps/posix/getaddrinfo.c
Last stack frame 0x7effe8e0, current 0x7effe9e8, size of last  264 = 0x108, total deeper  10424 = 0x028b8 =  10.2KB

#16 0x000104c0 in main () at test-getaddrinfo.c:16
16     int result = getaddrinfo("test.example.com", "80", &hints, &address_list);
Last stack frame 0x7effe9e8, current 0x7effeeb0, size of last 1224 = 0x4c8, total deeper  11648 = 0x02d80 =  11.4KB

Initial frame selected; you cannot go up.

对于每个堆栈帧,这报告了前一帧的大小,以及总更深层的堆栈(即堆栈中的累积空间)。我们正在寻找的是大框架。

浏览此滚动,有几个“最后一个”的位置超过1000字节。一般来说,超过一百多的东西非常大。在嵌入式系统上尤其如此,其中整个堆栈可能只是千字节或两个,无论是裸机还是每次RTOS螺纹。

然后在每种情况下查看前面的帧,我们看到以下嫌疑人(我已经编辑了堆栈行走输出以将“最后一个”线条与其相应的帧一起排列,并消除了1000字节以下的所有内容):

#5  0x76e05eec in __GI_ns_samename ([email protected]=0x7effcaf8 "test.example.com", b=0x7effcf38 "test.example.com", [email protected]=0x402 <error: Cannot access memory at address 0x402>) at ns_samedomain.c:196
5 Last stack frame 0x7effc2c0, current 0x7effcae8, size of last 2088 = 0x828, total deeper   2488 = 0x009b8 =   2.4KB

#6  0x76dff850 in __GI___res_nameinquery (name=0x402 <error: Cannot access memory at address 0x402>, [email protected]=0x0, type=1, class=1, [email protected]=0x7effe088 "~\027\201\203", 
    [email protected]=0x7effe888 "_nss_dns_get\210\340\377~") at res_send.c:287
6 Last stack frame 0x7effcae8, current 0x7effcf28, size of last 1088 = 0x440, total deeper   3576 = 0x00df8 =   3.5KB

#7  0x76dff984 in __GI___res_queriesmatch (buf1=0x7effd4e0 "~\027\001", [email protected]=0x7effd40c "n", eom1=0x7effd502 "", [email protected]=0x7effd40c "n", buf2=0x7effe088 "~\027\201\203", eom2=0x7effe888 "_nss_dns_get\210\340\377~")
    at res_send.c:342
7 Last stack frame 0x7effcf28, current 0x7effd368, size of last 1088 = 0x440, total deeper   4664 = 0x01238 =   4.6KB

#11 0x76dfe37c in __libc_res_nquerydomain ([email protected]=0x76fa1b50 <_res>, name=0x7effe088 "~\027\201\203", [email protected]=0x10550 "test.example.com", [email protected]=0x0, class=1, [email protected]=0, type=439963904, 
    [email protected]=2130700444, answer=0x7effe088 "~\027\201\203", [email protected]=0x9d <error: Cannot access memory at address 0x9d>, anslen=2048, [email protected]=1994515264, [email protected]=0x7effe894, 
    [email protected]=0x7effe898, nanswerp2=0x7effe89c, [email protected]=0x7effe8a4, [email protected]=0x7effe8a0, [email protected]=0x7effe8a4) at res_query.c:592
11 Last stack frame 0x7effd780, current 0x7effdbe0, size of last 1120 = 0x460, total deeper   6832 = 0x01ab0 =   6.7KB

#12 0x76dfe764 in __GI___libc_res_nsearch (statp=0x76fa1b50 <_res>, name=0x10550 "test.example.com", class=0, [email protected]=1, type=2130700444, [email protected]=439963904, answer=0x7effe088 "~\027\201\203", [email protected]=2048, 
    [email protected]=0x7effe894, [email protected]=0x7effe898, [email protected]=0x7effe89c, [email protected]=0x7effe8a0, [email protected]=0x7effe8a4) at res_query.c:376
12 Last stack frame 0x7effdbe0, current 0x7effe068, size of last 1160 = 0x488, total deeper   7992 = 0x01f38 =   7.8KB

#13 0x76e1e340 in _nss_dns_gethostbyname4_r ([email protected]=0x10550 "test.example.com", [email protected]=0x7effe998, buffer=0x7effea88 "\177", buflen=1024, [email protected]=0x7effe99c, [email protected]=0x7effe9ac, 
    [email protected]=0x0) at nss_dns/dns-host.c:326
13 Last stack frame 0x7effe068, current 0x7effe8e0, size of last 2168 = 0x878, total deeper  10160 = 0x027b0 =   9.9KB

#15 0x76f1f010 in __GI_getaddrinfo (name=<optimized out>, service=<optimized out>, hints=<optimized out>, pai=0x7effeeb0) at ../sysdeps/posix/getaddrinfo.c:2391
15 Last stack frame 0x7effe9e8, current 0x7effeeb0, size of last 1224 = 0x4c8, total deeper  11648 = 0x02d80 =  11.4KB

这是一个呼叫的结果 getaddrinfo()。它在做什么需要这么多空间?

切换到框架15并拆卸当前功能,即 __gi_getaddrinfo()。请注意,GDB说没有这样的文件,因为这是一个预构建的库函数,所以离开/ s选项。该功能很长,所以我刚刚显示了序言:

(gdb) f 15
#15 0x76f1f010 in __GI_getaddrinfo (name=<optimized out>, service=<optimized out>, hints=<optimized out>, pai=0x7effeec0) at ../sysdeps/posix/getaddrinfo.c:2391
2391 ../sysdeps/posix/getaddrinfo.c: No such file or directory.

(gdb) disassemble 
Dump of assembler code for function __GI_getaddrinfo:
   0x76f1eef0 <+0>: push {r4, r5, r6, r7, r8, r9, r10, r11, lr}
   0x76f1eef4 <+4>: add r11, sp, #32
   0x76f1eef8 <+8>: ldr r6, [pc, #2712] ; 0x76f1f998 <__GI_getaddrinfo+2728>
   0x76f1eefc <+12>: sub sp, sp, #1184 ; 0x4a0

PROLOGUERUMED OFF函数将使用的许多寄存器(7加上通常的FP和LR)。最后一行将帧扩展为1184字节,因此我们在堆栈步行中列出了1224个字节的大部分。添加了32个字节,即寄存器保存需求,我们得到1216.现在已经足够接近了。

为什么这个功能需要这样一个大的堆栈框架?我们没有源......但是我们有源文件名和部分路径。对“sysdeps / posix / getaddrinfo.c”的快速互联网搜索起来了一个网站,列出了这个文件的版本: WOBOQ GetAddrinfo源代码。惊人的!

GDB表示我们目前在文件的第2391号线,呼叫 Gaih_inet. (框架14)。搜索源代码页面, getaddrinfo() 呼叫 Gaih_inet.() at a different line, 2265:

2263       struct scratch_buffer tmpbuf;
2264       scratch_buffer_init (&tmpbuf);
2265       last_i = gaih_inet (name, pservice, hints, end, &naddrs, &tmpbuf);

这意味着此列表不是用于我们正在运行的库的完全相同版本。再次,这现在足够接近。

其中一个论点 Gaih_inet.()TMPBUF.,在第2263行中定义的局部变量。任何时候您看到大型分配,都称为“缓冲区”是良好的调查候选人。点击 scratch_buffer. 将我们带到其结构声明:

64 /* Scratch buffer.  Must be initialized with scratch_buffer_init
65    before its use.  */
66 struct scratch_buffer {
67   void *data;    /* Pointer to the beginning of the scratch area.  */
68   size_t length; /* Allocated space at the data pointer, in bytes.  */
69   union { max_align_t __align; char __c[1024]; } __space;
70 };

BAM再次!它包含1024个字节的字符数组。

既然我们有可导航的源代码,我们可以按照堆栈上的此过程遵循此过程。框架13, _nss_dns_gethostbyname4_r(),是堆栈漫步的下一个大型一个,有2168字节。检查其序幕:

(gdb) f 13
#13 0x76e1e340 in _nss_dns_gethostbyname4_r ([email protected]=0x10550 "test.example.com", [email protected]=0x7effe9a8, buffer=0x7effea98 "\177", buflen=1024, 
    [email protected]=0x7effe9ac, [email protected]=0x7effe9bc, [email protected]=0x0) at nss_dns/dns-host.c:326
326 nss_dns/dns-host.c: No such file or directory.

(gdb) disassemble 
Dump of assembler code for function _nss_dns_gethostbyname4_r:
   0x76e1e268 <+0>: push {r4, r5, r6, r7, r8, r9, r10, r11, lr}
   0x76e1e26c <+4>: add r11, sp, #32
   0x76e1e270 <+8>: ldr r4, [pc, #812] ; 0x76e1e5a4 <_nss_dns_gethostbyname4_r+828>
   0x76e1e274 <+12>: sub sp, sp, #76 ; 0x4c

同样,9个寄存器被推动,但是框架仅扩展了76个字节。所以不同的事情正在发生。

搜索WOBOQ搜索框中的函数名称。即使线路编号与版本偏斜的行数不匹配,它们是关闭的,并且正确的文件名正在显示出来,匹配GDB报告,帮助确认我们正在查看正确的代码。

检查该代码,该函数没有那些大刻刮刀结构中的一个,但是另一个悬疑的线是这个,分配2048个字节,靠近报告的帧大小,当您添加了寄存器保存的32个字节和76字节框架扩展:

364   host_buffer.buf = orig_host_buffer = (querybuf *) alloca (2048);

那看起来相似 Malloc(), 正确的?但 Malloc() 是否堆分配,而不是堆叠。然而,事实证明 Alloca() 如上所述,是堆栈分配器 这里.

一点拆卸拆卸,我们看到了这个SP的操纵,所以必须是实现 Alloca():

   0x76e1e2bc <+84>: sub r3, sp, #2048 ; 0x800
   0x76e1e2c0 <+88>: ldr r1, [pc, #736] ; 0x76e1e5a8 <_nss_dns_gethostbyname4_r+832>
   0x76e1e2c4 <+92>: ldr r4, [pc, #736] ; 0x76e1e5ac <_nss_dns_gethostbyname4_r+836>
   0x76e1e2c8 <+96>: sub sp, r3, #8

对于框架12, __gi ___ libc_res_nsearch():

(gdb) f 12
#12 0x76dfe764 in __GI___libc_res_nsearch (statp=0x76fa1b50 <_res>, name=0x10550 "test.example.com", class=0, [email protected]=1, type=2130700460, [email protected]=439963904, 
    answer=0x7effe098 "<\202\201\203", [email protected]=2048, [email protected]=0x7effe8a4, [email protected]=0x7effe8a8, 
    [email protected]=0x7effe8ac, [email protected]=0x7effe8b0, [email protected]=0x7effe8b4) at res_query.c:376
376 res_query.c: No such file or directory.

(gdb) disassemble 
Dump of assembler code for function __GI___libc_res_nsearch:
   0x76dfe640 <+0>: push {r4, r5, r6, r7, r8, r9, r10, r11, lr}
   0x76dfe644 <+4>: sub sp, sp, #1120 ; 0x460

这看起来像熟悉的堆栈帧扩展为1120字节。但是搜索Woboq for“__gi___libc_res_ns_nsearch”找不到匹配。尝试消除一些看起来像库前缀的一些东西,并在名称中搜索“res_nsearch”。这让我们到了几个结果,其中一个是res_query.c,文件gdb列为包含该函数。框架11是 __libc_res_nquerydomain() 在相同的源文件中。虽然看看源文件不太清楚,但我们可以看到框架12发生了什么,我们可以看到有一个函数 res_nquerydomain() in it.

为什么命名混乱?我认为它与Glibc命名约定和图书馆符号形成有关。

环顾11和10,我们似乎处于正确的位置,但框架10个电话的功能, __libc_res_nsend(),即使在剥离名称时,也不会在源文件中显示为呼叫。所以我们似乎正在追踪。也许这是图书馆版本差异追赶我们。

但是在文件中的一些其他函数上环顾四周,我们可以在此版本的文件中看到, res_nquerydomain() 呼叫 context_querydomain_common(),呼叫 __res_context_querydomain(),具有此本地变量,另一个“缓冲区”:

568         char nbuf[MAXDNAME];

maxdname有多大?单击它显示:

79 #define MAXDNAME        NS_MAXDNAME

搜索NS_MAXDNAME显示:

59 #define NS_MAXDNAME        1025        /*%< maximum domain name */

这是另一个BAM!这两个符号显示为若干函数的本地缓冲区大小。因此,我们的版本是我们的图书馆版本的另一个大型本地缓冲区,大小为大量最大的最大域名域名字符串。

继续框架7, __gi ___ res_queriesmatch() 在file res_send.c中。在WOBOQ搜索框中输入该文件名让我们获取它。搜索功能名称的尾随部分,我们找到 res_queriesmatch()。滚动它,这跳了出来:

377                 char tname[MAXDNAME+1];

框架6的完全相同的故事, __gi ___ res_nameinquery()。因此,这些巨大的缓冲区在呼叫堆栈中重复分配。框架5, __gi_ns_samename() 在ns_samedomain.c中,有这条行:

191         char ta[NS_MAXDNAME], tb[NS_MAXDNAME];

Gaaaah,其中两个!具有讽刺意味的是,我们使用的字符串只是“test.example.com”。缓冲区需要大小为最大可能的名称,但假设在每个级别都非常浪费。

我们对额外的11.4KB堆栈进行了答案,可以将域名翻译成IP地址。对于像Linux这样的通用操作系统,这并不是一个很大的事。但这绝不会在一个小嵌入式系统上飞行。

这就是为什么你看到嵌入式系统的自定义库,简化了TCP / IP堆栈等。除此之外,它们可能会限制域名到更短的字符串。这示出了通用编码之间的差异之一,例如桌面和服务器和嵌入式系统。

现在我们有另一个切线。来源 ns_samename() 显示它呼叫 strcasecmp()。但GDB回溯显示它呼叫 _dl_runtime_resolve() 在文件sysdeps / arm / dl-trampoline.s中。有趣,一个具有奇怪名称的汇编语言文件!

WOBOQ搜索显示了许多架构特定于DL-Trampoline的版本。我们想要ARM版本。它从这行开始:

1 /* PLT trampolines.  ARM version.

哎呀是plt蹦床?这是代码 过程链接表 触发动态库加载,然后跳转到库中的功能,如下所述 PLT并获得 - 代码共享和动态库的关键.

为什么“蹦床”一词?我猜因为函数的第一个调用者运行到存根中,导致库加载和修复操作,然后反弹到实际功能中。后续呼叫者将跳转到函数,跳过蹦床。

听起来像是有趣的东西!


要发布回复评论,请单击连接到每个注释的“回复”按钮。发布新的评论(不是回复评论),请在评论的顶部查看“写评论”选项卡。

注册将允许您参加所有相关网站的论坛,并为您提供所有PDF下载。

注册

我同意 使用条款隐私政策.

尝试我们偶尔但流行的时事通讯。非常容易取消订阅。
或登录