LINUX平台可以用GDB进行反汇编和调试。
- 格式:doc
- 大小:47.50 KB
- 文档页数:8
linux sdk命令的用法-回复Linux SDK命令的用法Linux Software Development Kit(SDK)是为开发人员提供的一组工具和库的集合,旨在帮助他们在Linux操作系统上开发软件。
该套件提供了丰富的命令行工具,可以用于编译、调试和管理Linux软件。
本篇文章将重点介绍Linux SDK命令的用法,并逐步回答与此相关的问题。
一、SDK安装与配置1. 如何安装Linux SDK?可以从供应商官网下载SDK安装包,并按照官方文档中提供的安装指南进行安装。
2. 如何配置Linux SDK?安装完成后,可以使用命令行工具进入SDK目录,并执行特定的配置命令进行配置。
二、SDK命令的用法1. 编译命令编译命令用于将源代码编译成可执行的二进制文件。
在SDK中,常用的编译命令包括gcc、g++等。
问题:如何使用gcc命令编译C源代码?回答:可以使用以下命令编译C源代码:gcc -o output_file source_file.c其中,output_file为生成的可执行文件名,source_file.c为C源代码文件名。
问题:如何使用g++命令编译C++源代码?回答:可以使用以下命令编译C++源代码:g++ -o output_file source_file.cpp其中,output_file为生成的可执行文件名,source_file.cpp为C++源代码文件名。
2. 调试命令调试命令用于在开发过程中对软件进行调试和排错。
在SDK中,常用的调试命令包括gdb、valgrind等。
问题:如何使用gdb命令进行调试?回答:可以使用以下命令启动gdb调试器:gdb binary_file其中,binary_file为待调试的可执行文件名。
在gdb调试器中,可以使用各种命令进行断点设置、变量查看、单步执行等操作,以便定位和修复问题。
问题:如何使用valgrind命令进行内存检查?回答:可以使用以下命令运行valgrind进行内存检查:valgrind leak-check=full binary_file其中,binary_file为待检查的可执行文件名。
课程名称:计算机组成与结构实验项目名称:bomblab专业班级:姓名:学号:指导教师:完成时间:2016 年 4 月20 日信息科学与工程学院根据以上分析,我们发现,从键盘输入一个值,放到(新)ebp+8进行调用,把他传到esp中,接着,函数再从$0x804a15c这个地址取值,放到中,接着,程序对这两个参数进行函数调用,调用判断字符串是否相等的程序equal进行判断,如果二者相等,则返回值为0,不引爆炸弹,反之,只要二者不相等,则炸弹爆炸。
分析结论:此处的密码存在地址$0x804a15c中,我们只要查看该地址的值,即可完成该题。
打开gdb调试,运行x/s 0x804a15c,查看该处的值故第一题的答案为We have to stand with our North Korean allies.Phase_5内容如下:0x08048db8 <+0>: push %ebp0x08048db9 <+1>: mov %esp,%ebp0x08048dbb <+3>: push %esi0x08048dbc <+4>: push %ebx这是两个调用者保存寄存器,因为接下来的循环中使用到了这两个寄存器的值,所以要进行压栈保存。
0x08048dbd <+5>: sub $0x20,%esp esp-320x08048dc0 <+8>: lea -0x10(%ebp),%eax0x08048dc3 <+11>: mov %eax,0xc(%esp)ebp-160x08048dc7 <+15>: lea -0xc(%ebp),%eaxPhase_6内容如下:0x08048c89 <+0>: push %ebp0x08048c8a <+1>: mov %esp,%ebp 0x08048c8c <+3>: push %edi这<phase_6+93>之前的都表达了什么?其实很简单,就是输入的这1到6的,且相邻两数不相等,且相差值不为好,接下来看<phase_6+93>之后的内容<phase_6+93>~<phase_6+145>,又是一个类似的功能块,操作,不妨仍仿照以上述方法做一次分析。
Linux加载vmlinux调试使⽤gdb加载内核符号表arm-eabi-gdb out/target/product/msm8625/obj/KERNEL_OBJ/vmlinux在内核的.config⾥⾯要打开 DEBUG_INFO和DEBUG_VM定位故障代码(gdb) l * qrd7627a_add_io_devices+0x1000xc07cd05c is in qrd7627a_add_io_devices (/home/yejialong/GH700C/kernel/arch/arm/mach-msm/msm8x25/goso-msm7627a-io.c:1851).1846 } else if (machine_is_msm8625q_skud() || machine_is_msm8625q_evbd()) {1847 #ifndef CONFIG_CALA021848 platform_device_register(&pmic_mpp_leds_pdev_skud);1849 #endif1850 /* enable the skud flash and torch by gpio leds driver */1851 platform_device_register(&gpio_flash_skud);1852 } else if (machine_is_msm8625q_skue()) {1853 /* enable the skue flashlight by gpio leds driver */1854 platform_device_register(&gpio_flash_skue);1855 }下⾯是mtk平台加载的⽅法,但是不管哪个平台都是差不多的,道理都是⼀样的。
weiqifa@weiqifa-Inspiron-3847:~/weiqifa/tm100$ ./prebuilts/gcc/linux-x86/arm/arm-eabi-4.7/bin/arm-eabi-gdb ./out/target/product/tm100/obj/KERNEL_OBJ/vmlinux GNU gdb (GDB) 7.3.1-gg2Copyright (C) 2011 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later </licenses/gpl.html>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law. Type "show copying"and "show warranty" for details.This GDB was configured as "--host=x86_64-linux-gnu --target=arm-linux-android".For bug reporting instructions, please see:</source/report-bugs.html>...Reading symbols from /home/weiqifa/weiqifa/tm100/out/target/product/tm100/obj/KERNEL_OBJ/vmlinux...done.(gdb)rockchip rk3399调试vmlinux./prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-android-addr2line -f -e kernel/vmlinuxweiqifa@dev:~/rk3399_7in1$ ./prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-android-addr2line -f -e kernel/vmlinux ffffff8008459f3c rk_iommu_domain_free/data/weiqifa/rk3399_7in1/kernel/drivers/iommu/rockchip-iommu.c:1005 (discriminator 2)weiqifa@dev:~/rk3399_7in1$RK平台tombstone调试./prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin/arm-eabi-addr2line -e ./out/target/product/rk3399_mid/symbols/system/lib/libspeexresampler.so 0x00002d54/proc/self/cwd/external/speex/libspeex/resample.c:376arm-eabi-gdb 先⽤命令找到这个东西,然后在去找去找到vmlinux 还有就是我arm-eabi-4.7/ 这个版本才可以⽤,这个是我试出来的。
linux修改寄存器值的方法Linux是一种开源的操作系统,它具有强大的定制性和灵活性。
在Linux中,可以通过修改寄存器值来实现对系统的控制和配置。
本文将介绍如何在Linux中修改寄存器值的方法。
一、什么是寄存器寄存器是计算机中的一种特殊的存储器,它用于存储CPU的指令、数据和控制信息。
在计算机硬件中,寄存器通常是由高速缓存存储器实现的,其速度比内存要快得多。
寄存器在计算机中起着举足轻重的作用,因为它们直接参与了CPU的运算和控制。
二、如何修改寄存器值在Linux中,要修改寄存器值,可以使用一些特定的命令和工具。
以下是一些常用的方法:1. 使用gdb调试工具gdb是一个功能强大的调试工具,可以用来调试程序并查看和修改寄存器值。
首先,通过命令行启动gdb:```gdb <程序名>```然后,在gdb的调试环境中,可以使用以下命令来查看和修改寄存器值:- info registers:查看所有寄存器的值;- print <寄存器名>:查看指定寄存器的值;- set <寄存器名>=<值>:修改指定寄存器的值。
2. 使用devmem2工具devmem2是一个用于读写物理内存和寄存器的工具。
首先,使用以下命令安装devmem2:```sudo apt-get install devmem2```然后,可以使用以下命令来查看和修改寄存器值:- devmem2 <寄存器地址>:查看指定寄存器的值;- devmem2 <寄存器地址> w <值>:将指定寄存器的值设置为指定的值。
3. 使用sysfs文件系统在Linux中,一些寄存器的值可以通过sysfs文件系统来查看和修改。
sysfs是一个虚拟文件系统,用于提供对设备和内核信息的访问。
通过sysfs文件系统,可以通过读写相应的文件来查看和修改寄存器值。
使用以下命令找到要修改的寄存器对应的文件:```find /sys -name <寄存器名>```然后,使用cat命令查看寄存器的值:```cat <寄存器文件>```使用echo命令修改寄存器的值:```echo <值> > <寄存器文件>```需要注意的是,修改寄存器值可能会影响系统的稳定性和正常运行。
(转载)gdb反汇编(转载)这⾥详细讨论⼀下disassemble/disass命令GDB⽂档:* 反汇编⼀个函数disass func_name*反汇编⼀段内存地址, 第1个参数是起始地址,第2个是终⽌地址disassemble 0×0 0×10* info line 命令来映射⼀个源码⾏到程序地址,然后使⽤命令disassemble显⽰⼀个地址范围的机器指令。
例1. 查看main函数从哪⾥开始(gdb) info line mainLine 34 of “rank.c” starts at address 0×804847fand ends at 0×8048493 .例2.(gdb) info line *0×804847fLine 34 of “rank.c” starts at address 0×804847fand ends at 0×8048493 .info line会修改 x/i 命令的默认的起始地址* disassemble不带参数,默认的反汇编范围是所选择帧的pc附近的函数单个参数, 就是pc, 当然也可以是函数名,因为函数名也是⼀个地址; 这样范围就是该pc附近的函数两个参数,就是内存地址范围* set disassembly-flavor intel将汇编指令格式设置为intel格式,默认是att(gdb) show disassembly-flavorThe disassembly flavor is “att”.* 使⽤x查看反汇编指令x/3i $pc显⽰pc开始的3条指令。
Linux下的段错误产生的原因及调试方法简而言之,产生段错误就是访问了错误的内存段,一般是你没有权限,或者根本就不存在对应的物理内存,尤其常见的是访问0地址.一般来说,段错误就是指访问的内存超出了系统所给这个程序的内存空间,通常这个值是由gdtr来保存的,他是一个48位的寄存器,其中的32位是保存由它指向的gdt表,后13位保存相应于gdt的下标,最后3位包括了程序是否在内存中以及程序的在cpu中的运行级别,指向的gdt是由以64位为一个单位的表,在这张表中就保存着程序运行的代码段以及数据段的起始地址以及与此相应的段限和页面交换还有程序运行级别还有内存粒度等等的信息。
一旦一个程序发生了越界访问,cpu就会产生相应的异常保护,于是segmentation fault就出现了.在编程中以下几类做法容易导致段错误,基本是是错误地使用指针引起的1)访问系统数据区,尤其是往系统保护的内存地址写数据最常见就是给一个指针以0地址2)内存越界(数组越界,变量类型不一致等)访问到不属于你的内存区域解决方法我们在用C/C++语言写程序的时侯,内存管理的绝大部分工作都是需要我们来做的。
实际上,内存管理是一个比较繁琐的工作,无论你多高明,经验多丰富,难免会在此处犯些小错误,而通常这些错误又是那么的浅显而易于消除。
但是手工“除虫”(debug),往往是效率低下且让人厌烦的,本文将就"段错误"这个内存访问越界的错误谈谈如何快速定位这些"段错误"的语句。
下面将就以下的一个存在段错误的程序介绍几种调试方法:1dummy_function(void)2{3unsigned char*ptr=0x00;4*ptr=0x00;5}67int main(void)8{9dummy_function();1011return0;12}作为一个熟练的C/C++程序员,以上代码的bug应该是很清楚的,因为它尝试操作地址为0的内存区域,而这个内存区域通常是不可访问的禁区,当然就会出错了。
AT&A汇编1.Register Reference引用寄存器要在寄存器号前加百分号%,如“movl %eax, %ebx”。
80386 有如下寄存器:[1] 8 个32-bit 寄存器 %eax,%ebx,%ecx,%edx,%edi,%esi,%ebp,%esp;( 8 个16-bit 寄存器,它们事实上是上面8 个32-bit 寄存器的低16 位:%ax,%bx,%cx,%dx,%di,%si,%bp,%sp;8 个8-bit 寄存器:%ah,%al,%bh,%bl,%ch,%cl,%dh,%dl。
它们事实上是寄存器%ax,%bx,%cx,%dx 的高8 位和低8 位;)[2] 6 个段寄存器:%cs(code),%ds(data),%ss(stack), %es,%fs,%gs;[3] 3 个控制寄存器:%cr0,%cr2,%cr3;[4] 6 个debug 寄存器:%db0,%db1,%db2,%db3,%db6,%db7;[5] 2 个测试寄存器:%tr6,%tr7;[6] 8 个浮点寄存器栈:%st(0),%st(1),%st(2),%st(3),%st(4),%st(5),%st(6),%st(7)。
2. Operator Sequence操作数排列是从源(左)到目的(右),如“movl %eax(源), %ebx(目的)”3. Immediately Operator使用立即数,要在数前面加符号$, 如“movl $0x04, %ebx”或者:para = 0x04movl $para, %ebx指令执行的结果是将立即数0x04 装入寄存器ebx。
4. Symbol Constant符号常数直接引用 如value: .long 0x12a3f2demovl value , %ebx指令执行的结果是将常数0x12a3f2de 装入寄存器ebx。
引用符号地址在符号前加符号$, 如“movl $value, % ebx”则是将符号value 的地址装入寄存器ebx。
linux sdk命令的用法-回复Linux SDK命令的用法Linux是一种开源操作系统,为开发者提供了丰富的软件开发工具和应用程序接口,以构建各种类型的应用程序。
Linux SDK(Software Development Kit)是一组用于开发Linux应用程序的工具、库和文档。
它包括编译器、调试器、开发库和示例代码等,为开发者提供了一站式的开发环境。
本文将逐步介绍Linux SDK命令的用法。
第一步:安装Linux SDK首先,我们需要在Linux系统上安装Linux SDK。
这通常可以通过包管理器或从官方网站下载SDK。
一旦安装完成,我们可以开始使用其中的命令和工具。
第二步:使用编译器Linux SDK包含了一个强大的编译器集合,用于将源代码编译成可执行文件。
最常用的是GNU编译器套件(GCC),它支持多种编程语言,如C、C++、Objective-C等。
下面是GCC的一些常用命令:1. gcc:编译C源代码文件。
例如,gcc -o hello hello.c将hello.c文件编译为名为hello的可执行文件。
2. g++:编译C++源代码文件。
类似于gcc命令,可以使用g++命令来编译C++程序。
3. gcc -c:只进行编译而不链接。
这个命令生成目标文件(.o文件),可以在后续的步骤中使用。
4. gcc -E:预处理源代码文件。
这个命令将宏展开、头文件包含等预处理指令应用到源代码文件中。
5. gcc -g:生成带有调试信息的可执行文件。
使用这个选项可以在调试程序时获得更多的信息。
除了GCC,Linux SDK还提供了其他编译器,如LLVM(低级虚拟机),它支持更多的编程语言和编译优化。
第三步:使用调试器在开发过程中,调试器是一个非常重要的工具,用于定位和修复代码中的错误。
Linux SDK提供了多个调试器,如GDB(GNU调试器)和LLDB(LLVM调试器)。
下面是一些调试器的使用命令:1. gdb:启动GDB调试器。
逆向分析的技巧逆向分析是指从软件程序或系统中逆向工程出有关其内部结构、功能实现和漏洞等信息的过程。
以下是一些常用的逆向分析技巧:1. 静态分析:通过分析程序的字节码、汇编代码或逆向反汇编的结果,来理解程序的结构和功能实现。
可以使用工具如IDA Pro、Ghidra、Radare2等来进行静态分析。
2. 动态分析:通过动态运行程序,观察其在运行时的行为,包括输入输出、函数调用、内存访问等,以了解其内部工作原理。
可以使用调试器如GDB、OllyDbg、WinDbg等来进行动态分析。
3. 反汇编和反编译:通过将二进制代码或汇编代码转换为高级语言代码,以更好地了解程序的工作方式。
反编译工具如IDA Pro、Ghidra、Radare2可用于将汇编代码转换为相应的高级语言代码。
4. 数据流分析:跟踪程序中的数据流,了解数据如何在不同的函数之间传递和变换。
这有助于理解程序的逻辑和漏洞的来源。
5. 逆向工程工具和库:利用已有的逆向工程工具和库,如Capstone、Keystone、PyTLink等,来简化逆向分析的过程。
6. 模式识别和模糊测试:通过分析相似的程序或系统,识别常见的设计模式或安全漏洞模式。
模糊测试可以通过输入大量的随机或非常规数据来找出程序的漏洞。
7. 调试符号和符号执行:通过启用调试符号和符号执行,可以在程序执行过程中跟踪和分析关键变量和函数的值和状态,以便更好地理解程序的内部工作原理。
8. 反混淆和解密:某些恶意软件或保护软件常常使用混淆和加密技术来隐藏其真实意图和功能。
逆向分析时,需要使用相应的工具和技术来反混淆和解密代码,以还原其原始形式。
以上是一些常用的逆向分析技巧,但应注意逆向分析的合法性和道德性,遵守法律法规和相关规定。
GDB章节GDB是linux下常用的应用程序调试工具,本文介绍linux环境下一些常用的调试命令,并通过交叉编译移植GDB到Android开发板上。
主机系统环境:Linux TS-Server2.6.32-21-generic-pae#32-Ubuntu SMP Fri Apr1609:39:35UTC 2010i686GNU/Linux目标板环境:Linux localhost3.0.31-00003-OMAP-Android-01831-gf9b35ae#4SMP PREEMPT Fri Sep708:19:55CST2012armv7l GNU/Linux交叉编译工具版本:gcc version4.4.1(Sourcery G++Lite2010q1-202)(target:arm-none-linux-gnueabi)GDB源码版本7.2:下载地址http://202.109.143.228:82/down/gdb-7.2.tar.gz或/gnu/gdb/gdb-7.3.1.tar.gz(官网最新版)GDB依赖库ncurses:下载地址/pub/gnu/ncurses/ncurses-5.8.tar.gz环境变量说明:export CC_PATH=/home/lichao/android/arm/opt/CodeSourcery/bin//交叉编译工具链路径export SRC_GDB=~/Download一.GDB常用命令命令介绍:gdb>list//列出源码,默认源码文件和执行文件同路径gdb><回车>//重复上次命令gdb>break10//在第十行下断点gdb>break func//在函数func入口下断点gdb>info break//查看断点信息gdb>info args//打印当前函数参数名和值gdb>info locals//打印当前函数所有局部变量和值gdb>disassemblefunc//查看函数func的汇编代码gdb>run//运行程序gdb>next//单条语句执行gdb>n//等同nextgdb>continue//继续运行gdb>p i//打印变量值,等同于printgdb>bt//查看函数堆栈gdb>shell<command>//执行shell命令gdb>clear<linenum>//清除断点,相关命令有delete/disable/enablegdb>step//单步运行二.移植GDB到Omap4460开发板(跑的是Android4.0)1.解压配置$mkdir~/gdb$cp$SRC_GDB/gdb-7.2.tar.gz~/gdb$cd~/gdb$tar xvf gdb-7.2.tar.gz-C.$cd gdb-7.2$export PATH=$CC_PATH:$PATH//追加交叉编译工具链至path中,请确保成功添加$./configure--host=arm-none-linux-gnueabi--prefix=/home/lichao/gdb/arm_gdb(上面指定的host表示目标主机所需的编译器前缀,而prefix有可能设置全路径而不是带环境变量的路径安装路径,请注意)以上都ok,如果有问题,请检查你的交叉编译工具链环境变量和相关操作是否正确。
LINUX平台可以用GDB进行反汇编和调试。 如果在Linux平台可以用gdb进行反汇编和调试。(转)
2. 最简C代码分析 为简化问题,来分析一下最简的c代码生成的汇编代码: # vi test1.c
int main() { return 0; }
编译该程序,产生二进制文件: # gcc test1.c -o test1 # file test1 test1: ELF 32-bit LSB executable 80386 Version 1, dynamically linked, not stripped
test1是一个ELF格式32位小端(Little Endian)的可执行文件,动态链接并且符号表没有去除。 这正是Unix/Linux平台典型的可执行文件格式。 用mdb反汇编可以观察生成的汇编代码:
# mdb test1 Loading modules: [ libc.so.1 ] > main::dis ; 反汇编main函数,mdb的命令一般格式为 ::dis main: pushl %ebp ; ebp寄存器内容压栈,即保存main函数的上级调用函数的栈基地址 main+1: movl %esp,%ebp ; esp值赋给ebp,设置main函数的栈基址 main+3: subl $8,%esp main+6: andl $0xf0,%esp main+9: movl $0,%eax main+0xe: subl %eax,%esp main+0x10: movl $0,%eax ; 设置函数返回值0 main+0x15: leave ; 将ebp值赋给esp,pop先前栈内的上级函数栈的基地址给ebp,恢复原栈基址 main+0x16: ret ; main函数返回,回到上级调用 >
注:这里得到的汇编语言语法格式与Intel的手册有很大不同,Unix/Linux采用AT&T汇编格式作为汇编语言的语法格式 如果想了解AT&T汇编可以参考文章:Linux AT&T 汇编语言开发指南
问题:谁调用了 main函数?
在C语言的层面来看,main函数是一个程序的起始入口点,而实际上,ELF可执行文件的入口点并不是main而是_start。 mdb也可以反汇编_start:
> _start::dis ;从_start 的地址开始反汇编 _start: pushl $0 _start+2: pushl $0 _start+4: movl %esp,%ebp _start+6: pushl %edx _start+7: movl $0x80504b0,%eax _start+0xc: testl %eax,%eax _start+0xe: je +0xf <_start+0x1d> _start+0x10: pushl $0x80504b0 _start+0x15: call -0x75 _start+0x1a: addl $4,%esp _start+0x1d: movl $0x8060710,%eax _start+0x22: testl %eax,%eax _start+0x24: je +7 <_start+0x2b> _start+0x26: call -0x86 _start+0x2b: pushl $0x80506cd _start+0x30: call -0x90 _start+0x35: movl +8(%ebp),%eax _start+0x38: leal +0x10(%ebp,%eax,4),%edx _start+0x3c: movl %edx,0x8060804 _start+0x42: andl $0xf0,%esp _start+0x45: subl $4,%esp _start+0x48: pushl %edx _start+0x49: leal +0xc(%ebp),%edx _start+0x4c: pushl %edx _start+0x4d: pushl %eax _start+0x4e: call +0x152 <_init> _start+0x53: call -0xa3 <__fpstart> _start+0x58: call +0xfb ;在这里调用了main函数 _start+0x5d: addl $0xc,%esp _start+0x60: pushl %eax _start+0x61: call -0xa1 _start+0x66: pushl $0 _start+0x68: movl $1,%eax _start+0x6d: lcall $7,$0 _start+0x74: hlt >
问题:为什么用EAX寄存器保存函数返回值? 实际上IA32并没有规定用哪个寄存器来保存返回值。但如果反汇编Solaris/Linux的二进制文件,就会发现,都用EAX保存函数返回值。 这不是偶然现象,是操作系统的ABI(Application Binary Interface)来决定的。 Solaris/Linux操作系统的ABI就是Sytem V ABI。 概念:SFP (Stack Frame Pointer) 栈框架指针 正确理解SFP必须了解: IA32 的栈的概念 CPU 中32位寄存器ESP/EBP的作用 PUSH/POP 指令是如何影响栈的 CALL/RET/LEAVE 等指令是如何影响栈的
如我们所知: 1)IA32的栈是用来存放临时数据,而且是LIFO,即后进先出的。栈的增长方向是从高地址向低地址增长,按字节为单位编址。 2) EBP是栈基址的指针,永远指向栈底(高地址),ESP是栈指针,永远指向栈顶(低地
址)。 3) PUSH一个long型数据时,以字节为单位将数据压入栈,从高到低按字节依次将数据存
入ESP-1、ESP-2、ESP-3、ESP-4的地址单元。 4) POP一个long型数据,过程与PUSH相反,依次将ESP-4、ESP-3、ESP-2、ESP-1从栈内弹出,放入一个32位寄存器。 5) CALL指令用来调用一个函数或过程,此时,下一条指令地址会被压入堆栈,以备返回时能恢复执行下条指令。 6) RET指令用来从一个函数或过程返回,之前CALL保存的下条指令地址会从栈内弹出到EIP寄存器中,程序转到CALL之前下条指令处执行 7) ENTER是建立当前函数的栈框架,即相当于以下两条指令: pushl %ebp movl %esp,%ebp 8) LEAVE是释放当前函数或者过程的栈框架,即相当于以下两条指令: movl ebp esp popl ebp
如果反汇编一个函数,很多时候会在函数进入和返回处,发现有类似如下形式的汇编语句:
pushl %ebp ; ebp寄存器内容压栈,即保存main函数的上级调用函数的栈基地址 movl %esp,%ebp ; esp值赋给ebp,设置 main函数的栈基址 ........... ; 以上两条指令相当于 enter 0,0 ........... leave ; 将ebp值赋给esp,pop先前栈内的上级函数栈的基地址给ebp,恢复原栈基址 ret ; main函数返回,回到上级调用
这些语句就是用来创建和释放一个函数或者过程的栈框架的。 原来编译器会自动在函数入口和出口处插入创建和释放栈框架的语句。 函数被调用时: 1) EIP/EBP成为新函数栈的边界 函数被调用时,返回时的EIP首先被压入堆栈;创建栈框架时,上级函数栈的EBP被压入堆栈,与EIP一道行成新函数栈框架的边界 2) EBP成为栈框架指针SFP,用来指示新函数栈的边界 栈框架建立后,EBP指向的栈的内容就是上一级函数栈的EBP,可以想象,通过EBP就可以把层层调用函数的栈都回朔遍历一遍,调试器就是利用这个特性实现 backtrace功能的 3) ESP总是作为栈指针指向栈顶,用来分配栈空间 栈分配空间给函数局部变量时的语句通常就是给ESP减去一个常数值,例如,分配一个整型数据就是 ESP-4 4) 函数的参数传递和局部变量访问可以通过SFP即EBP来实现 由于栈框架指针永远指向当前函数的栈基地址,参数和局部变量访问通常为如下形式: +8+xx(%ebp) ; 函数入口参数的的访问 -xx(%ebp) ; 函数局部变量访问
假如函数A调用函数B,函数B调用函数C ,则函数栈框架及调用关系如下图所示: +-------------------------+----> 高地址 | EIP (上级函数返回地址) | +-------------------------+ +--> | EBP (上级函数的EBP) | --+ <------当前函数A的EBP (即SFP框架指针) | +-------------------------+ +-->偏移量A | | Local Variables | | | | .......... | --+ <------ESP指向函数A新分配的局部变量,局部变量可以通过A的ebp-偏移量A访问 | f +-------------------------+ | r | Arg n(函数B的第n个参数) | | a +-------------------------+ | m | Arg .(函数B的第.个参数) | | e +-------------------------+ | | Arg 1(函数B的第1个参数) | | o +-------------------------+ | f | Arg 0(函数B的第0个参数) | --+ <------ B函数的参数可以由B的ebp+偏移量B访问 | +-------------------------+ +--> 偏移量B | A | EIP (A函数的返回地址) | | | +-------------------------+ --+ +--- | EBP (A函数的EBP) |<--+ <------ 当前函数B的EBP (即SFP框架指针) +-------------------------+ | | Local Variables | | | .......... | | <------ ESP指向函数B新分配的局部变量 +-------------------------+ | | Arg n(函数C的第n个参数) | | +-------------------------+ | | Arg .(函数C的第.个参数) | | +-------------------------+ +--> frame of B