GCC 和 NASM 联合编译,汇编函数前要有引到下划线 _

   为了学习的顺利进行,今天尝试复习 C 语言和汇编语言的联合编译。代码很简单:

/* main.c */

#include <stdio.h>

extern int Caculate(int nX, int nY);


char strFormat[] = " %d + %d = %d\n";

int MyAdd(int nX, int nY);


int main()
{
	int nX = 3, nY = 2, nResult = 0;

	nResult = Caculate(nX, nY);
	printf(strFormat, nX, nY, nResult);

	return 0;
}

int MyAdd(int nX, int nY)
{
	return nX + nY;
}
; proc.nas

extern strFormat

extern MyAdd           ; int MyAdd(int nX, int nY);
extern printf


global Caculate        ; int Caculate(int nX, int nY);


[SECTION .data]
	nResult dw 0

[SECTION .text]
Caculate:
	push ebp            ; 保存父函数的栈基址
	mov ebp, esp        ; 设置本函数的栈基址,即父函数的栈顶
	sub esp, 0F0h       ; 给子函数开辟栈空间

	; 取入口参数
	mov ebx, [ebp + 12] ; 取参数2
	mov eax, [ebp + 8]  ; 取参数1

	push ebx            ; 参数入栈
	push eax
	call MyAdd          ; 函数的返回值保存在 eax 寄存器中
	mov [nResult], eax  ; 保存 _MyAdd 的返回值
	add esp, 2 * 4      ; 两个参数出栈

	push eax
	mov ebx, [ebp + 12]
	mov eax, [ebp + 8]
	push ebx
	push eax
	push strFormat
	call printf
	add esp, 4 * 4

	mov eax, [nResult]  ; 设置本函数的返回值
	leave               ; 相当于下面两条指令的效果
;   mov esp, ebp        ; 恢复父函数的栈顶指针(= 本函数的栈基址)
;   pop ebp             ; 恢复父函数的栈基址
	ret
#MakeFile

ALL:
    nasm -felf32 proc.nas
    gcc -m32 -o main main.c proc.o

    依然采用 GCC + NASM,可以通过编译,可是链接成执行文件怎么都通不过,一直报错“未定义函数”!

    尝试各种操作系统(WinXP 32位、Win7 64位)、各种编译器版本(MinGW 32 位、64 位)、各种编译参数,搞了大半天,死活不让我过!

    就在快绝望的时候,突然想起以前浏览的 C 语言库函数,依稀记得函数名前面都带有个下划线 “_”!是不是就是这个原因呢?试试,只需修改汇编代码,涉及联合编译的变量和函数都加个下划线前缀“_”。。。。。。

; proc.nas

extern _strFormat

extern _MyAdd           ; int MyAdd(int nX, int nY);
extern _printf


global _Caculate        ; int Caculate(int nX, int nY);


[SECTION .data]
	nResult dw 0

[SECTION .text]
_Caculate:
	push ebp            ; 保存父函数的栈基址
	mov ebp, esp        ; 设置本函数的栈基址,即父函数的栈顶
	                    ; 如果本函数有栈入口参数,则[bp + (n - 1) * 4]是第 n 个参数
	sub esp, 0F0h       ; 为本函数的局部变量和调用中断、子函数时保存现场数据的栈开辟空间

	; 取入口参数
	mov ebx, [ebp + 12] ; 取参数2
	mov eax, [ebp + 8]  ; 取参数1

	push ebx            ; 参数入栈
	push eax
	call _MyAdd         ; 函数的返回值保存在 eax 寄存器中
	mov [nResult], eax  ; 保存 _MyAdd 的返回值
	add esp, 2 * 4      ; 两个参数出栈

	push eax
	mov ebx, [ebp + 12]
	mov eax, [ebp + 8]
	push ebx
	push eax
	push _strFormat
	call _printf
	add esp, 4 * 4

	mov eax, [nResult]  ; 设置本函数的返回值
	leave               ; 相当于下面两条指令的效果
;   mov esp, ebp        ; 恢复父函数的栈顶指针(= 本函数的栈基址)
;   pop ebp             ; 恢复父函数的栈基址
	ret

    竟然真是的,编译通过了!

    总结,汇编语言(NASM)和 C 语言(GCC)联合编译的时候(在 Windows 系统下),汇编代码中和 C 语言产生混编关系的变量和函数,前面都要加一个下划线“_”!C 语言代码中不加。


转载于:https://my.oschina.net/u/580100/blog/525442

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代 码 风 格(1) 随着程序功能的增加和版本的提高,程序越来越复杂,源文件也越来越多,风格规范的源程序会对软件的升级、修改和维护带来极大的方便,要想开发一个成熟的软件产品,必须在编写源程序的时候就有条不紊,细致严谨。 在编程中,在程序排版、注释、命名和可读性等问题上都有一定的规范,虽然编写可读性良好的代码并不是必然的要求(世界上还有难懂代码比赛,看谁的代码最不好读懂!),但好的代码风格实际上是为自己将来维护和使用这些代码节省时间。本节就是对汇编语言代码风格的建议。 变量和函数的命名 1. 匈牙利表示法 匈牙利表示法主要用在变量和子程序的命名,这是现在大部分程序员都在使用的命名约定。“匈牙利表示法”这个奇怪的名字是为了纪念匈牙利籍的Microsoft程序员Charles Simonyi,他首先使用了这种命名方法。 匈牙利表示法用连在一起的几个部分来命名一个变量,格式是类型前缀加上变量说明,类型用小写字母表示,如用h表示句柄,用dw表示double word,用sz表示以0结尾的字符串等,说明则用首字母大写的几个英文单词组成,如TimeCounter,NextPoint等,可以令人一眼看出变量的含义来,在汇编语言中常用的类型前缀有: b 表示byte w 表示word dw 表示dword h 表示句柄 lp 表示指针 sz 表示以0结尾的字符串 lpsz 表示指向0结尾的字符串的指针 f 表示浮点数 st 表示一个数据结构 这样一来,变量的意思就很好理解: hWinMain 主窗口的句柄 dwTimeCount 时间计数器,以双字定义 szWelcome 欢迎信息字符串,以0结尾 lpBuffer 指向缓冲区的指针 stWndClass WNDCLASS结构 … 很明显,这些变量名比count1,abc,commandlinebuffer和FILEFLAG之类的命名要易于理解。由于匈牙利表示法既描述了变量的类型,又描述了变量的作用,所以能帮助程序员及早发现变量的使用错误,如把一个数值当指针来使用引发的内存页错误等。 对于函数名,由于不会返回多种类型的数值,所以命名时一般不再用类型开头,但名称还是用表示用途的单词组成,每个单词的首字母大写。Windows API是这种命名方式的绝好例子,当人们看到ShowWindow,GetWindowText,DeleteFile和GetCommandLine之类的API函数名称时,恐怕不用查手册,就能知道它们是做什么用的。比起int 21h/09h和int 13h/02h之类的中断调用,好处是不必多讲的。 2. 对匈牙利表示法的补充 使用匈牙利表示法已经基本上解决了命名的可读性问题,但相对于其他高级语言,汇编语言有语法上的特殊性,考虑下面这些汇编语言特有的问题: ● 对局部变量的地址引用要用lea指令或用addr伪操作,全局变量要用offset;对局部变量的使用要特别注意初始化问题。如何在定义中区分全局变量、局部变量和参数? ● 汇编的源代码占用的行数比较多,代码行数很容易膨胀,程序规模大了如何分清一个函数是系统的API还是本程序内部的子程序? 实际上上面的这些问题都可以归纳为区分作用域的问题。为了分清变量的作用域,命名中对全局变量、局部变量和参数应该有所区别,所以我们需要对匈牙利表示法做一些补充,以适应Win32汇编的特殊情况,下面的补充方法是笔者提出的,读者可以参考使用: ● 全局变量的定义使用标准的匈牙利表示法,在参数的前面加下划线,在局部变量的前面加@符号,这样引用的时候就能随时注意到变量的作用域。 ● 在内部子程序的名称前面加下划线,以便和系统API区别。 如下面是一个求复数模的子程序,子程序名前面加下划线表示这是本程序内部模块,两个参数——复数的实部和虚部用_dwX和_dwY表示,中间用到的局部变量@dwResult则用@号开头: _Calc proc _dwX,_dwY local @dwResult finit fild _dwX fld st(0) fmul ;i * i fild _dwY fld st(0) fmul ;j * j fadd ;i * i + j * j fsqrt ;sqrt(i * i + j * j) fistp @dwResult ;put result mov eax,@dwResult ret _Calc endp 本书中所有的示范源代码采用的都是这样的命名约定。 代码的书写格式 1. 排版方式 程序的排版风格应该遵循以下规则。 首先是大小写的问题,汇编程序中对于指令和寄存器的书写是不分大小写的,但小写代码比大写代码便于阅读,所以程序中的指令和寄存器等要采用小写字母,而用equ伪操作符定义的常量则使用大写,变量和标号使用匈牙利表示法,大小写混合。 其次是使用Tab的问题。汇编源程序中Tab的宽度一般设置为8个字符。在语法上,指令和操作数之间至少有一个空格就可以了,但指令的助记符长度是不等长的,用Tab隔开指令和操作数可以使格式对齐,便于阅读。如: xor eax,eax fistp dwNumber xchg eax,ebx 上述代码的写法就不如下面的写法整齐: xor eax,eax fistp dwNumber xchg eax, ebx 还有就是缩进格式的问题。程序中的各部分采用不同的缩进,一般变量和标号的定义不缩进,指令用两个Tab缩进,遇到分支或循环伪指令再缩进一格,如: .data dwFlag dd ? .code start: mov eax,dwFlag .if dwFlag == 1 call _Function1 .else call _Function2 .endif … 合适的缩进格式可以明显地表现出程序的流程结构,也很容易发现嵌套错误,当缩进过多的时候,可以意识到嵌套过深,该改进程序结构了。
编译过程: 1). 解压后默认的文件夹位置是在D:\Linux-0.11,如果你不是将文件解压到该目录下, 你要修改MinGW32目录下的MinGW32.bat文件,将里面的PATH指向MinGW32的bin目录. 2). 打开Linux-0.11目录,双击MinGW32.bat快捷方式,打开控制台. 3). make 一下,生成1.44M的Boot.img软盘镜像,要清除编译结果请"make clean" 4). 如果安装了bochs,直接双击bochsrc.bxrc即可运行Linux-0.11了. 5). 也可用其它虚拟机加载Boot.img后运行,如果出现Kernel panic,请把虚拟机里的硬盘删了 6). 在出现Insert root floppy and press ENTER以后,将rootimage-0.11.img载入虚拟软驱,回车 这就是能在windows环境下编译的Linux 0.11了,不是在Cygwin,也不是在虚拟机里,而是使用MinGW. 下面是在Windows下编译Linux 0.11会遇到的问题和对原文件作的修改: 1.赵炯博士已经将汇编程序中引用的C变量(包括嵌入汇编的C变量)的下划线去掉了,但MinGW的gcc可能是为了与其它Windows下的编译器保持兼容,并不能识别这些不带下划线的C变量,因而还得把原先已经在汇编程序中去掉下划线的C变量加上下划线,同时也要把被C程序引用的汇编程序中的变量加上下划线. 2.MinGW中不带as86编译器,因而把boot目录下原先用as86编译的bootsect.s和setup.s两个程序修改成能用nasm编译的程序.并且更名为bootsect.asm和setup.asm. 3.在Makefile作的主要修改: 在LDFLAGS中加了--image-base 0x0000 将elf_i386改成i386pe 将cd 与 make 之间的;改成&,如cd kernel ; make 改成cd kernel & make MinGW中没有sync这个程序,可以把它注释掉,更简单的办法是写一个sync.c,这个sync.c只包含一个空的main函数,编译成sync.exe 因为类似的原因,make dep会出错 4.生成的system文件是PE格式的(PE是Portable Executable的简称),这是windows下的可执行文件的格式,显然是不能直接执行的,必须加以转化.我实现了通过两种方式加以转化. 1)写一个程序Trans.cpp将system.exe里的代码和数据从PE文件里解析出来,生成一个system.bin文件,这个文件是能被setup模块直接加载的.我已经将这个程序放在了Linux-0.11的tools目录下,要微软的编译编译. 2)自己写一个PE Loader,这种方式比较麻烦,但是想想自己也能做一个PE Loader,还是满有成就感的,尽管这是一个最简单的Loader.代码是加在Linux-0.11-With-PE-Loader\boot目录下的setup.asm文件里,里面有详细的注释. 5.对tools下的build.c作了修改,使其能生成可引导的1.44M的软盘镜像文件Boot.img 6.在Link的过程中,init目录下的main.c会出现以下错误: boot/head.o(.text+0x540c):fake: undefined reference to `_main' init/main.o(.text+0x16f):main.c: undefined reference to `_alloca' init/main.o(.text+0x174):main.c: undefined reference to `__main' make: *** [tools/system.exe] Error 1 第一个和最后一个错误还好理解,但中间那个错误那就莫明其妙了,因为Linux 0.11根本没有这个函数,在gcc编译选项里也有-nostdinc .有一个解释是main函数不是一个普通的函数,MinGW gcc会对它作特殊的处理.解决的办法其实也很简单,把main.c下面的main函数改名为_main,或者是干脆把它改成另外一个函数,就改成start吧.记得把head.s里的_main也改了. 在最后,要感谢《自己动手写操作系统》的作者于渊,其实我也是先将原先只能在Linux下编译的书里源代码用MinGW移植到Windows下编译的过程中才试着在Windows下编译Linux 0.11源代码的,有了在Windows下编译Linux 0.11源代码的经验,移植高版本的源代码,像0.12,0.95,0.96等等版本应该不会有太大的麻烦了。 也要感谢Linux内核完全注释的作者赵炯博士,是他拉接了操作系统操作系统爱好者的距离. 最后,我也非常想和操作系统爱好者们共同交流心得体会,也希望能多认识一些朋友. 我的网名:flyfish 我的QQ:785606288 E-mail:I2CBus@126.com 另外,要转载请保持本文件的完整性,请尊重别人的劳动果实. 修改日志: 08/3/29 修改了一下Makefile,旧的Makefile在某些文件更新后还会重新编译。 修改了Trans.cpp中的一个dug,该dug在translate MinGW gcc编译的程序时可能会出错。用MinGW gcc 编译的程序的VirtualAddress的形式可能是0xFFC1000这样的形式,其实0x1000才是它的VirtualAddress 08/4/2 修改了下MinGW32.bat,现在已经不用重设路径了。 08/4/4 Trans.cpp还是有错,如果VirtualAddress>0xffff,那么生成的system.bin就错了,bochs调试时会一直重启。 权宜之计,把0xffff再改成0x3ffff,这样VirtualAddress就不能大于0x3ffff,不知谁有更好的解决方法,

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值