AT&T汇编函数是如何声明的?
很简单用.type命令来创建函数的标签,如下:
.type fun1, @function
fun1:
保存寄存器等值。
函数体
ret
Ps:函数一开始可以使用finit命令来清空FPU寄存器。
还有pusha和popa很方便的让你全部的寄存器值一次性都保存在堆栈中和取出
那么如何访问汇编函数呢?
直接call+函数标签 就OK了。
函数调用是如何传值的?
通过栈和寄存器。注意如果是寄存器的话,那么类型必须相同,所以我估计在类型不同的时候如果进行进行不同类型传值,强制类型转换是在传值之前就已经解决了的。
至于到低是怎么传值的,按照C样式来的话,从参数从右到左入栈。
这也就解释了为什么
Int a;
f(a++,++a);
第一个参数会是2.应该是通过从右到左边解析表达式,然后入栈的。
注意不光存储传递的值,还要保存调用函数处的地址。
所以栈中就像下面这样:
函数参数。。 |
函数参数2 |
函数参数1 |
返回地址 |
旧EBP |
局部变量1 |
局部变量2 |
局部变量。。 |
关于这个EBP值。
因为在函数中,你的栈顶指针ESP随时可能变化,所以为了简化问题,确保每次寻找地址都是定长的。所以引入了另外一个寄存器EBP,来指向就旧的EBP这个位置。那么函数参数只用n(%ebp),n表示偏移量,n大于0。如果n小于0,就是指向局部变量(高地址在上)。那么函数参数和局部变量都可以通过地址来表示了。因此这样.data基本上可以说就不需要了。之前我也说过,.data可能更多的用于保存常量。
所以C语言中的自动变量的问题也解释了。因为当函数调用结束,ret之后EBP值恢复到原先的值。自动的变量的值仍然存储在栈中,但是你已经找不到了。估计C语言之前只能支持在函数开头定义变量也是因为这个吧。
注意前面的call指令只支持将返回地址压入栈中。所以其余东西都是靠程序员来手动完成的。在ret之前一定要恢复ESP和EBP的值。
一个linux程序是如何构成的?
1. 一开始,linux为要执行的程序在内存中创建一个区域。然后linux将程序的虚拟地址通过映射,转换到物理地址上真正区域运行。
2. Linux分配给程序运行的虚拟内存地址从0x80480000开始到0xbfffffff(7个f 即2G-3G)。
3. 内存区域中的第一块区域是包含汇编程序的所有指令和数据。这个还包含一系列的运行程序的连接过程所需的指令信息
4. 内存区域中的第二块区域是程序的栈。
实际上栈中的顶部并不是从0xbfffffff开始的。它还包含一些程序运行的所需路径和命令行参数等。
结构如下:
环境变量 命令行参数 |
指向环境变量的指针 |
0x00000000 |
指向命令行参数3的指针 |
指向命令行参数2的指针 |
指向命令行参数1的指针 |
程序名称 |
ESP--->参数数目 |
这几天刚好在看《UNIX高级环境编程》,这个里面就说了。如果要增加环境变量必须要重新分配空间来存储这些命令行参数的指针。(减少就直接减少就行了)
并且C语言中argc和argv两个参数也可以理解了。
不过有些书上说是3个环境变量,加上了envp。其实就是指向环境变量的指针。
在我参考的汇编书中,堆栈说的就是栈,但是实际上堆和栈是分开的两个位置。并且在个人实际编写代码的时候了解到,栈的空间比堆的空间小得多。
注意:
可能有比较丰富C编程经验的会知道,大的数组只能开在外面。其实这个区域存放全局和静态变量,但是既不是堆也不是栈,而是静态数据区(bss段)。我一开始就以为是在堆中,但其实堆是由运行时使用系统调用分配的。而栈在程序开始运行的时候就已经分配好了。(从APUE书上看,堆似乎也是分配好的,我想如果在运行过程中动态的变化,那这个数据结构一定是能够支持变化的)
关于linux系统调用:
通常用EAX保存系统调用值。然后int0x80进入中断。
当然还有其他函数需要传递。
EBX ECX EDX ESI EDI中分别存放第2~5个参数。
并且系统调用之后的返回值存储在EAX寄存器中。
如何跟踪系统调用?
使用strace。使用就是strace直接跟上可执行文件的路径。
下面是一些参数
-c | 统计每个系统调用的时间、调用和错误 |
-d | 显示strace的一些调试输出 |
-e | 指定输出的过滤表达式 |
-f | 在创建紫禁城的时候跟踪它们 |
-ff | 如果写入到输出文件,则把每个子进程写入到单独的文件中 |
-i | 显示执行系统调用时的指令指针 |
-o | 把输出写入到指定文件 |
-p | 附加到由PID指定的现有进程 |
-q | 抑制关于附加和分离的消息 |
-r | 对每个系统调用显示一个相对的时间戳 |
-t | 把时间添加到每一行 |
-tt | 把时间添加到每一行,包括微妙 |
-ttt | 添加epoch形式的时间(从1970年1月1日开始的秒数),包括微妙 |
-T | 显示每个系统调用花费的时间 |
-v | 显示系统调用信息的不经省略的版本 |
-x | 以十六进制格式显示所有非ASCII字符 |
-xx | 以十六进制格式显示所有字符串 |
这东西对于测试程序效率,或者学习系统调用的效率之类的真是很有用。
并且这个东西可以用于系统上的任何程序!
并且还可以监视已经运行在系统上的程序的能力。
只用strace+PID就OK了。
关于在线判题系统是如何防止不怀好意的人使用系统调用。可以通过ptrace来判断。
这是一个做过OJ的人告诉我的。貌似网上也有资料,有兴趣的可以去查查看。
再多一句嘴,一般的系统调用速度是比标准库速度要快的。因为标准库也是调用系统调用的。不过在部分标准IO调用方面由于与系统调用的设置不同,标准IO库有BUFFER。这就得看具体的情况而定了。
最后今天刚在贴吧看到一个题目。http://tieba.baidu.com/p/2367967516
其实这个可以用上面说的函数栈针来做。
void xym5366()
{
char *p=""xym 是大骗子\n";
*(&(*(&p+22)))=p;
}
这里的+22,我一开始直接用的+4。。结果不对。。估计VC吧所有的寄存器都压入函数栈中了吧。。比上面所说的压入栈的多一些。