转载自:http://blog.sina.com.cn/s/blog_7a1c18a80101fv4x.html
Gdb调试Fortran中的堆和栈
(2014-02-27 15:24:03)
转载▼
标签: gdbfortran堆栈 | 分类: fortran |
今天来讨论一下Fortran中的各种变量是如何在堆和栈中储存的。
有关堆(heap)和栈(stack)的概念可以去看斯坦福大学的开放课程:编程方法(Programming.Methodology.CS106A)。不过课堂上使用的是java语言,而且网上找不到有关Fortran语言中变量是如何在堆和栈中储存的。因此,本文中将使用一个简短的例子演示一下Fortran程序内部工作原理,顺便演示如何使用gnu中的gdb调试程序。
首先介绍如何使用gdb命令来查看函数及变量在内存的位置,gdb使用方法可参考《Linux C编程一站式学习》
Backtrace(bt) | 查看调用函数的栈帧 |
Info(i) | 查看局部变量:I locals |
Frame(f) | 选择栈帧 |
Finish | 让当前函数运行,直到返回为止 |
Set var | 改变变量值 |
X | 打印储存单元内容,全部看做字节,而不区分是哪个变量的字节 |
写一个比较简单的程序,在main函数中调用sub1子函数,查看各个函数栈帧在内存中位置以及变量储存位置。
Gdb调试过程
各条命令详细解释:
- list展示源程序
- b 7 设置断点于源程序的第7行
- start 开始运行程序
- s = step,单步运行,运行到调用子程序时进入子程序
- bt 显示函数调用的栈帧
- i locals 显示当前函数局部变量
- f 1 返回栈帧为1的main函数
- i locals 显示main函数局部变量
查询内存存储情况
Examine memory: x/FMT ADDRESS.
ADDRESS is an expression for the memory address to examine.
FMT is a repeat count followed by a format letter and a size letter.
Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal),t(binary), f(float), a(address), i(instruction), c(char) and s(string).
Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes).
The specified number of objects of the specified size are printed according to the format.
Defaults for format and size letters are those previously used.
Default count is 1. Default address is following last thing printed with this command or "print".
根据说明可以看出,gdb中x的作用是显示内存地址中所储存的变量值。
都说变量名是函数地址的别名,但是x后面直接跟变量名并不能正确显示。
可以看到,第一次使用x/a temp运行时,会将变量temp的变量值10自动"替换"temp,从而显示的0xa地址。而只有使用了取址符号&后,才正确显示出函数地址处的变量值。
另外,在sub函数内查询U、V是无法正确查到地址以及变量值的,只有使用frame函数转到main函数的栈上,才能显示main函数的U、V变量。在sub函数内只有x、y、temp、z可正确查询。
变量 | 内存中位置(16进制) | 值(16进制的地址格式) |
U | 0x28abfc | 0x0000000a |
V | 0x28abf8 | 0x0000000b |
W | 0x28abf4 | 0x00000015 |
Temp | 0x28aa74 | 0x0000000a |
x | 0x28abfc | 0x0000000a |
y | 0x28abf8 | 0x0000000b |
z | 0x28abf4 | 0x00000015 |
从上面内存地址可以看出,fortran在传递参数时是按址传递,也就是直接传递实参的地址给子函数,这样子函数中参数的改变也会导致实参相应的改变。另外,在栈上是从高地址向低地址扩展的(0x28abfc→0x28abf4),可是子函数是哪里储存的,地址0x28aa74又是从何而来?
使用gdb反汇编命令disassemble,对主函数main和子函数sub进行反汇编查看。
这样查看有一个缺点,那就是无法与源程序对应,查看响应源程序语句的汇编命令。
使用$objdump –dS main命令,将源代码与汇编代码穿插显示。下面只截取main和sub函数部分。
使用watch $esp及watch $ebp指令,能够每步执行时查询栈顶及栈底指针变化情况。
指令 | esp | ebp |
0x00401199 <+9> | 0x28aa80 | 0x28ac08 |
0x00401170 <+0> <-into sub | 0x28aa7c | |
0x00401171 | 0x28aa78 | |
0x00401173 | 0x28aa78 | |
0x00401176 <+6> | 0x28aa68 | |
0x0040118f | 0x28aa7c | 0x28ac08 |
0x004011c0 <-return main | 0x28aa80 | |
0x00401264 | 0x28aa7c |
从这里可以看出,main函数的栈帧是从0x28aa80→0x28ac08,而sub子函数栈帧为0x28aa68→0x28aa78。Temp变量储存位置为0x28aa74恰好位于sub子函数栈帧中。
各个函数栈帧示意图:
0x28ac08 | main函数栈底 |
…… | |
0x28abfc | U储存位置 |
0x28abf8 | V储存位置 |
0x28abf4 | W |
…… | |
0x28aa80 | main函数栈顶 |
…… | |
0x28aa78 | sub函数栈底 |
…… | |
0x28aa68 | sub函数栈顶 |