java c混合编程 linux_如何在64位的linux系统上使用汇编和C语言混合编程

最近在看于渊的一个操作系统的实现,在第五章的时候汇编和C 同时使用时碰到了问题:代码如下

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.pngfoo.s

1 extern choose

2

3 ;;;;;the data area

4 num1st dd 3

5 num2nd dd 4

6

7 global _start

8 global myprint

9

10

11 _start:

12

13 push dword [num1st]

14 push dword [num2nd]

15

16 call choose

17 add esp,8

18

19 mov ebx,0

20 mov eax,1

21 int 0x80

22 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

23 ;;;; function protype :void myprint(char *msg, int len)

24 ;;;; display the message

25 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

26

27 myprint:

28

29 mov ecx,[esp+4]

30 mov edx,[esp+8]

31 ;mov edx,esi

32 ;mov ecx,edi

33

34 mov ebx,1

35 mov eax,4

36 int 0x80

37 ret

38

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.pngbar.c

1 /************ bar.c *****************/

2

3 void myprint(char *msg,int len);

4

5 int choose(int a,int b)

6 {

7 if (a>=b) {

8 myprint("the 1st one\n",13);

9 }

10

11 else {

12 myprint("the 2st one\n",13);

13 }

14

15 return 0;

16 }

编译和链接的时候使用的指令:(AMD处理器,64位操作系统)

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png编译链接指令

1 nasm -f elf foo.s -o foo.o

2 gcc -c bar.c -o bar.o

3 ld -s -o foobar bar.o foo.o

汇编语言用nasm编写并用nasm编译器编译,而C语言用的是gcc编译,这些都没有问题,但是在链接的时候出错了,提示如下:

ld: i386 architecture of input file `foo.o' is incompatible with i386:x86-64 output

google了一下,意思就是nasm 编译产生的是32位的目标代码,gcc 在64位平台上默认产生的是64位的目标代码,这两者在链接的时候出错,gcc在64位平台上默认以64位的方式链接。

这样在解决的时候就会有两种解决方案:

<1> 让gcc 产生32位的代码,并在链接的时候以32位的方式进行链接

在这种情况下只需要修改编译和链接指令即可,具体如下:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png32位的编译链接指令

1 nasm -f elf foo.s -o foo.o

2 gcc -m32 -c bar.c -o bar.o

3 ld -m elf_i386 -s -o foobar foo.o bar.o

具体的-m32 和-m elf_i386 请自行查阅gcc (man gcc)

如果你是高版本的gcc(可能是由于更新内核造成的),可能简单的使用-m32 的时候会提示以下错误(使用别人的历程,自己未曾遇到):

> In file included from /usr/include/stdio.h:28:0,

> from test.c:1:

> /usr/include/features.h:323:26: fatal error: bits/predefs.h: No such file or directory

> compilation terminated.

这应该是缺少构建32位可执行程序缺少的包,使用以下指令安装:

sudo apt-get install libc6-dev-i386

此时应该就没有什么问题了。

参考地址:http://aaronbonner.tumblr.com/post/14969163463/cross-compiling-to-32bit-with-gcc

<2> 让nasm以64位的方式编译产生目标代码,并让gcc的连接器以默认的方式链接

但是第二种方法并不是仅仅更改nasm的编译方式那么简单,因为64位平台跟32位平台有很大的不同,包括参数的传递,指令集等。所以如果怕麻烦的话完全可以使用第一种方法,让gcc产生32位的目标代码,因为32位的代码可以运行在64位的平台上,这应该就是所谓的向上兼容。不过64位将来应该会是主流,所以研究一下还是很有必要的。

首先对gcc 产生的32位与64位的汇编语言进行对比:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png32位

1 gcc -m32 -S -o bar.s -c bar.c

2

3 /************** 32 位的汇编语言 *************/

4 choose:

5 .LFB0:

6 .cfi_startproc

7 pushl %ebp

8 .cfi_def_cfa_offset 8

9 .cfi_offset 5, -8

10 movl %esp, %ebp

11 .cfi_def_cfa_register 5

12 subl $24, %esp

13 movl 8(%ebp), %eax

14 cmpl 12(%ebp), %eax

15 jl .L2

16 movl $13, 4(%esp)

17 movl $.LC0, (%esp)

18 call myprint

19 jmp .L3

20 .L2:

21 movl $13, 4(%esp)

22 movl $.LC1, (%esp)

23 call myprint

movl8(%ebp),

%eax

cmpl12(%ebp),

%eax

jl .L2

movl$13,

4(%esp)

movl$.LC0,

(%esp)

上面只取了我们感兴趣的地方:ebp指向的是刚进入choose函数的堆栈栈顶指针,此时只想的是刚入栈的ebp的值,ebp+4指向的函数调用入栈的ip地址(这里应该是段内调用,具体原因不太清楚,因为两个文件之间调用函数属于段内还是段外,我真的不清楚,如果你知道,可以告诉我),ebp+8指向的是调用者压栈的第二个参数,也是从左边数第一个参数,ebp+12 是调用者压栈的第一个参数,也就是从左边数第二个参数。这样我们知道了c语言的参数传递机制,就能编写相应的汇编程序调用C语言了,而C 语言调用汇编函数则以此类推,先将第二个参数压栈,再将第一个参数压栈。不再赘述。

(

例:void fun(int a, int b) 函数在调用fun时首先将参数b 压栈,然后将参数a压栈,这样fun 函数在取参数的时候就能先取a了,然后再取b,因为堆栈是先入后出。如果这样你还不明白,建议你看一下赵迥老师的linux 0.11内核完全剖析的第三章。

)

(

注:rax:64位,eax:32位ax:16位

movl:

移动32位,movq:移动64位,movd:移动16位,movb:移动8位

其他带标志的指令类似。

)

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png64位

1 gcc -S -o bar.s -c bar.c (64位的操作系统默认)

2 /************** 64位的汇编程序 ***********/

3

4

5 choose:

6 .LFB0:

7 .cfi_startproc

8 pushq %rbp

9 .cfi_def_cfa_offset 16

10 .cfi_offset 6, -16

11 movq %rsp, %rbp

12 .cfi_def_cfa_register 6

13 subq $16, %rsp

14 movl %edi, -4(%rbp)

15 movl %esi, -8(%rbp)

16 movl -4(%rbp), %eax

17 cmpl -8(%rbp), %eax

18 jl .L2

19 movl $13, %esi

20 movl $.LC0, %edi

21 call myprint

22 jmp .L3

23 .L2:

24 movl $13, %esi

25 movl $.LC1, %edi

26 call myprint

movl%edi,

-4(%rbp)

movl%esi,

-8(%rbp)

movl-4(%rbp),

%eax

cmpl-8(%rbp),

%eax

jl .L2

movl$13,

%esi

movl$.LC0,

%edi

注意64位下的参数传递有了改变,而且寄存器也有了改变,不过我们既然使用了nasm汇编,对于64位寄存器的改变暂时不必操心,只需要先关心参数传递的格式。

可以看出参数传递不是用压栈的方式传递了,而是使用的寄存器来传递给被调用者,再由被调用者将其压栈使用。上述代码显示先将第一个参数给edi,然后由被调用者压入-4(%rbp),然后再将第二个参数给esi,由被调用者要入-8(%rbp),这一点倒是和32位下参数的入栈方式一致。

至于用寄存器传递函数参数取代用堆栈传递函数参数的原因,个人感觉是函数的调用者不用再操心入栈和释放栈了,完全由被调用者操心,至少我在函数的调用者里面经常是记得给函数参数入栈,但是函数调用完成后却忘记了把栈恢复。

这样我们就能根据上述规则来修改我们的foo.s,使其能够与64位的gcc产生的目标代码链接在一起。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png64位模式下的foo.s

1 /**************foo.s**************/

2 extern choose

3

4 ;;;;;the data area

5 num1st dd 3

6 num2nd dd 4

7

8 global _start

9 global myprint

10

11

12 _start:

13

14

15 mov edi,[num1st]

16 mov esi,[num2nd]

17 call choose

18

19 mov ebx,0

20 mov eax,1

21 int 0x80

22 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

23 ;;;; function protype :void myprint(char *msg, int len)

24 ;;;; display the message

25

26 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

27

28 myprint:

29

30 ;mov ecx,[esp+4]

31 ;mov edx,[esp+8]

32 mov ecx,edi

33 mov edx,esi

34

35 mov ebx,1

36 mov eax,4

37 int 0x80

38 ret

至于用寄存器传递函数参数的规则见以下参考资料:

==========================================

版权为win_hate 所有, 转载请保留作者名字

我这段时间要把以前的一个x86_32 的linux 程序移植到x86_64(AMD) 的linux 环境里. 由于写的是数学算

法, 64 与32 位有很大不同, 代码实际上要重写. 看了点资料后, 觉得AMD64 的扩展于以前16 到32 位的扩展很类

似, e**, 扩展为r**, 此外还多了8个通用寄存器r8~r15.指令格式与32位的极为相似. 我觉得比较容易, 所以没再仔细看, 就开

始动手写了.

我的程序由若干个汇编模块于与若干个c模块构成, 很多c模块要调用汇编模块. 作为试验, 我先写了个简单的汇编函数, 然后用c来调用. 结果

算出来的值始终是错误的. 这令我很恼火, 因为函数很简单, 没有多少出错的余地. 后来我把程序反汇编出来, 错误马上浮现出来了, 函数的参数居然

是通过寄存器来传递的. 我凭以前的经验, 从堆栈里取参数, 算出的结果当然不对了. 我以前不是没碰到过用寄存器传递参数的情况, 但所在的环境都不

是pc. 在x86_32/linux 中, 即使用-O3 优化选项, gcc 仍通过栈来传递参数的.

所以我们现在知道, 在x86_64/linux/gcc3.2 中, 即使不打开优化选项, 函数的参数也会通过寄存器来传递, 这肯定是阔了的表现(通用寄存器多了).

我试验了多个参数的情况,发现一般规则为, 当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。当参数为7 个以上时, 前6 个与前面一样, 但后面的依次从"右向左" 放入栈中。

例如:

CODE

(1) 参数个数少于7个:

f (a, b, c, d, e, f);

a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%r8, f->%r9

g (a, b)

a->%rdi, b->%rsi

有趣的是, 实际上将参数放入寄存器的语句是从右到左处理参数表的, 这点与32位的时候一致.

CODE

2) 参数个数大于7 个的时候

H(a, b, c, d, e, f, g);

a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%rax

g->8(%esp)

f->(%esp)

call H

易失寄存器:

%rax, %rcx, %rdx, %rsi, %rdi, %r8, %r9 为易失寄存器, 被调用者不必恢复它们的值。

显然,这里出现的寄存器大多用于参数传递了, 值被改掉也无妨。而%rax, %rdx 常用于

数值计算,%rcx 常用于循环计数,它们的值是经常改变的。其它的寄存器为非易失的,也

就是rbp, rbx, rsp, r10~r15 的值如果在汇编模块中被改变了,在退出该模块时,必须将

其恢复。

教训:

用汇编写模块, 然后与c 整合, 一定要搞清楚编译器的行为, 特别是参数传递的方式. 此外, 我现在比较担心的一点是, 将来如果要把程序移植

到WIN/VC 环境怎么办? 以前我用cygwin的gcc来处理汇编模块, 用vc来处理c模块, 只需要很少改动. 现在的问题是, 如果VC用

不同的参数传递方式, 那我不就麻烦了?

补充:

前面的参数a, b, c, d 等, 都是整数, 长整数, 或指针, 也就是说, 能放到寄存器里头的. 如果你要传递一个很大的结构, 我估计编译器也只能通过栈来传递了.

环境为AMD Athlon64, Mandrak linux 9.2, GCC3.3.1

==============================

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值