在C语言中调用汇编语言子程序 程序连接失败

在C语言中调用汇编语言子程序 程序连接失败,第1张

是饥肢皮因为你的c语言编译器是 32 位的 ,而你的汇编语言是 16 位的 ,连接当然有饥者问题,就算没问题也是运行不起来的。

建议你换一个 32 位的汇编编译器 且用32位汇编指令编写 , 不过你得重新学一下 32位的烂差汇编。

另外还有一种方法就是 使用VC 编写C语言 , 在C中直接用内联汇编。代码如下

__stdcall int fun()

{

int a

__asm{

mov eax, [esi+4]

add eax, [esi+6]

mov a , eax

}

return a

}

int main(int argc, char* argv[])

{

printf("Hello World!\n")

printf("%d\n",fun())

return 0

}

c语言可以嵌套汇编:

按照TC2.0的帮助系统所以说的,在TC2.0下是可以用汇编的,方法是使用asm关键字:其格式是:

asm opcode <operands><newline>,如同别的注释一样,<>之间的表示可选的例如:

main()

{

char *c="hello,world/n/r$"

asm mov ah,9asm mov dx,casm int 33

printf("You sucessed!/n")

}

或者是:

main()

{

char *c="hello,world/n/r$"

asm mov ah,9

asm mov dx,c

asm int 33

printf("You sucessed!")

}

两种格式其实是一种.如果你用的是第一种的样式,记住:

每一句汇编语句都要以asm开头,如果一行内有多个句子,

那么千万不要忘记在两个句子之间的这个semicolon(分号),

但是最后一句汇编后面(如果后面没有其它的语句)的分备凯号可有可无,象第一个例子中的

asm int 33后面的分号就可以不要,因为它的后面没有其它

的语句了.但如果是这样:

asm mov ah,9asm mov dx,casm int 33printf("You sucessed!")

那么asm int 33后面的分号便还是留下好,以免出现编译错误!

在这一点上颇象C语言.

还有一种格式是

asm{ assembly language statement},这种格式应该被普遍的欢迎.

它们的例子如下(其中的语句排列格式与上面两种相同):

asm{

mov ax,var1

add ax,var2

......

}

但是要注意这种格式TC2.0是不支持的!

只有后来的TC++3.0及后来的IDE支持!

工具的使用:

一旦你的C源文件里包括了这些好东西,则必须用TCC.EXE的COMMAND-LINE来编译,具肢滚如体的命令参数TCC.EXE已经提供,这里不复阐述了.最简单的是:TCC C源文件名(使用这个方法,TCC会自动调用TASM.EXE和TLINK.EXE,并且能够使TLINK.EXE正确的找到需要的.obj和.lib文件,如果你单步编译的话,可能会碰到很多的问题,主要是TLINK.EXE它自己并不会去找.obj和.lib文件,你自己可以建一个.bat文件,如果要指定.lib文件的目录的话可以用/L参数,在文章的后面有一个例子).但大家要注意了,看一下你的TC目录下面到底是否有TASM.EXE文件,并在TURBOC.CFG(这个文件包括TCC.EXE运行期参数,这里面所有参数在运很期都将被自动TCC.EXE使用,例如:-IH:/TC/INCLUDE/

-LH:/TC/LIB/)文件中设置好一些参数,并确认TASM.EXE的版本号要2.0以上,以及是否能够向下兼容.但是在大多数的情况下TC的目录是没有TASM.EXE的,或是版本不正常.

如果你有TASM.EXE文件并且TURBOC.CFG文件也已经写好了,但是还要注意一个

问题:运行TCC.EXE时要在独立的DOS SHELL下面(不要害怕,这不是一个新东西,我的意思

是,不在诸如TC下的DOS SHELL下面运行,我曾经败在这个问题下,当我发现时直想揍电脑

一顿,还好没有,不然就没有这篇文件了.)

还有一句重要的话:TC2.0支持大部分8086指令(当然用法有一些约定,不过现在我并不打算

进行详细说明,因为那是一件很繁杂的事,以后有时间或许会写出来----如果大家需要的话).

如果说上面我所说的那些约定很繁杂的话,那么下面的方法该是多么简单啊!

让我们使用Borland为TC2.0内建的变量来进行伪汇编.

或许你还不知道在TC2.0中还有一些内建的pseudo寄存器(可以看作是register 型的变量,但是它们比register型的变量好用的多)

_AX,_AH,_AL,

_BX,_BH,_BL,

_CX,_CH,_CL,

_DX,_DH,_DL,

_DI,_SI,_SP,

_CS,_DS,_ES,_SS

注意这些寄存器的size,_AX,_BX,_CX,_DX,_CS,_DS,_ES,_SS,_SI,_DI,_SP等都是16位的寄存器相当于C语言的unsigned int类型,其余的都是8位的寄存器(相当于unsigned char)(TC怎么可能支持32位的寄存历启呢,所以EAX等是不能用的,FS,GS和IP寄存器都是无效的),还有就是在传递参数的时候千万不要忘记使用强制类型转换.

中断调用指令是:__int__(interrupt_#)(注意int的前辍和后辍都是两个underscores)

For example:

#include<dos.h>

unsigned int _stklen=0x200

unsigned int _heaplen=0

main()

{

_DX=(unsigned int)"Hello,world./r/n$"

_AX=0x900

__int__(0x21)

}

dos.h它是包含__int__()内建中断调用语句的头文件,因此是不可

缺少的._stklen和_heaplen是定义运行期堆栈和堆大小的两个内部

引用变量(这是个我自己想的名词,意指如果这两个变量在源文件中

显式的声明了,那么编译程序会自会引用来构造编译时期的信息以产生

用户希望的目标文件,如果不显式的声明则编译程序自动确定).

这两个变量也有一些约定,如果_stklen不显式声明,_heaplen赋值为零

都表示栈和堆都是defult的.

最后在TC2.0中还有一个没有说明的标志位寄存器flags,它也是内建

pseudo寄存器是:_FLAGS,是一个16位寄存器.这些内建的寄存器都可以进行

运算,但是要注意它们所代表的类型(必要时进行类型转换)

看起来这是不是一种好的办法啊(而且使用这种方法只要用个一个dos.h头文件就好,

不需要用TCC编译,可以直接在TC20的IDE下编译).

TC2.0中也提供了一些简单好用的函数来实现对DOS功能的调用如:

int86(...),int86x(...)(但是这些方法实际仍然要调用函数,所以不如使用

伪寄存器,又因为要牵涉到union REGS结构的内存分配所以系统的开销是增大了,

而使用伪寄存器是最简洁的),端口通信函数如:inportb(...),inport(...),

outportb(...),outport(...),指针转换函数:FP_OFF,FP_SEG,MK_FP,这些函数在

帮助系统中都有,有用时大家可以查阅.

tlinkbat.bat的例子:

rem The lib environment variable is the directory of the .obj and .lib file

set lib=h:/tc/lib/

rem 这下面的句子中的c0s(C 零S)是一个.OBJ文件,是一个C程序的STARTUP文件

tlink %lib%c0s %1,%1,%1,/L%lib%emu.lib %lib%maths.lib %lib%cs.lib

set lib=

(使用时可将以rem开头的句子删除)

___________________________________________________

一些约定:

我们先说一下在TC20下写汇编(内联汇编--自己起的名字,大家可以想叫什么叫什么)时的编译器的编译原则:

1.所有在main()函数外的的汇编语言的语句都作为数据声明语句处理,也即在编译器编译时会将它放在数据段中,如:

asm string1 db "Hello",,,'world!',0ah,0xd,"$"

main()

{

asm mov dx,offset string1

asm mov ah,9

asm int 33

asm mov dx,offset string2

asm int 33

}

asm string2 db "the string can be declared after the main() function!$"

象这些样子在main()外面的汇编语言的数据定义语句(事实上不管是什么汇编语句,

只要是在main()之外,包括这个句子:asm mov ax,0x4c00),在编译后都放在数据段中,而C语言的数据声明语句仍按C的规则!

2.所有在main()函内的汇编语言的语句在编译后都放在代码段中,包括这个句子:

asm string2 db "the string can be declared after the main() function!$"

3.不要在以asm 开头的语句中使用C语言的关键字,这会导致编译阶段的错误

那么,根据这三条大家会得到什么样的结论呢?(先闭上眼想一想,你可能会由此变的

很赞赏自己,是的你应该这样相信自己是对的!)

让我们一起看一下这个结论:

1.根据编译原则1得到:不可以在main()外面写汇编命令语句(不要笑,正是与C语言相同才值得注意!),在任何地方都不要进行任何的段定义和宏定义(这是因为编译后的形式决定的,也即:在TC20下所有的汇编格式的语句只能是,直接性的数据定义和语句指令)!

2根据编译原则2得到:不可以在main()之内使用汇编的语句进行数据定义(同样不要笑,

大多数人在第一次在TC20下写汇编都会有这样的错误的)

3.如同类强制类型这样的事是不可以在以asm开头的汇编语句中使用的

好了,天即朗,气瞬清!这样一说,一个大体的框架就出来了!只要遵守这个原则写,就可避免很多莫名其妙的错误出现!

通俗的说:

汇编语句的数据定义放在main()外面,指令放在main()里面.

如果你没有更好的文档,那么记住我的这些话!

一些细节的问题:

在以asm开头的内联汇编语句中是不支持C的转义字符的,但是用C语言声明一个字符数组(含有转义字符的),然后用int 33 ah=9这功能时输出这个字符串时,其中的转义字符是有效的(这主要是因为编译后其内部表示形式不同造成的,自己想想会有答案的).

内联汇编支持C的一些如数值表示,字符串声明格式等,

如:一个十六进制的数据可以用两种方式表示:0xa 和0ah,字符串可以是这样:

"Hello,world!$"(如同C)也可以这样'Hello,world!$'(用汇编自己的方式).

象C一样你同样要注意赋值的类型,而且要比C更严格(汇编从来不自己动手做

如同类型转换啊这样事),所以一切的事完全要你自己做好!而且你不要企图以C的形式

做这件事,如这样的格式 asm mov dx,(unsigned)a(a是一个这样的东西,

char a[ ]="hello,world!"),而且这样句子也会导致错误:asm mov dx,word ptr a(逻辑错误),不过这不是在编译时的错误,而是运行期的错误(具体的原因自己想一想,象word label这样的东西的运算作用和会导致的后果),你可以这样用一个句子做"中间人"如int i=(unsigned)aasm mov dx,i(也千万不要用asm mov dx,(unsigned)a 这样的句子.但是,告诉大家一个好消息,你可以用指针指向一个字符串,然后你会惊讶你竟然可以这样:

char *p="hello,world"asm mov dx,p,然后用int 33 ah=9的功能输出这个字符串而不会有错误(这也表现出指针的特点,它是一个二字节的(TC20下)变量,含有的是一个地址,这与其指向的变量的类型是毫无关系的).

内汇汇编语句不支持->这个运算符.还有标号的问题,在最后的例子中你会年看到一些特别之处!

上面所说的只是很细小并微少的一些事(也是很常遇到的),尚有很多的细节要说,但由于本人时间有限不能一一列举,如C的结构在内联汇编的应用等大家可以按照其运行机理去想想一下用法另外,由于这只是一件学习的事,所以还是大家自己学(找一下有关文档,当然现在已经没有什么比较完整的了),情况会好的多,我在对内联汇编的学习过程中领会到了不少的东西,例如编译原理方面的知识,以及如何做会使代码更高效,占空间最少等的方法.最后向大家推荐一种方法,在利用TCC的-S开关可以生成C源文件的汇编代码

(或许很多的人都用过)是很好的学习材料!祝大家学有所成!

Cstarter

02-11-17

/* 由于个人的时间和能力有限,难免有错误和不详细的地方,请大家见谅!

My Email:[email protected] [email protected] QQ:170594633 */

一些例子:

下面这个例子是对沈美明 温冬婵的

<<IBM-PC 汇编语言程序设计>>清华版第十一章程序的改写

可直接在命令行上键入 tcc filename 就可以,当然你要有TASM.EXE

/*

asm mus_frep dw 330,294,262,294,3 dup(330)

asm dw 3 dup(294),330,392,392

asm dw 330,294,262,294,4 dup(330)

asm dw 294,294,330,294,262,-1

asm mus_time dw 6 dup(25),50

asm dw 2 dup (25,25,50)

asm dw 12 dup(25),100

*/

asm mus_frep dw 330,392,330,294,330,392,330,294,330

asm dw 330,392,330,294,262,294,330,392,294

asm dw 262,262,220,196,196,220,262,294,330,262

asm dw -1

asm mus_time dw 3 dup (50),25,25,50,25,25,100

asm dw 2 dup (50,50,25,25),100

asm dw 3 dup (50,25,25),100

main()

{

asm jmp start

/*设置发声的频率,这一段在沈美明 温冬婵的

<<IBM-PC 汇编语言程序设计>>清华版第十一章有详细的说明 */

sound:

asm mov al,0b6h

asm out 43h,al

asm mov dx,12h

asm mov ax,533h*896

asm div di

asm out 42h, al

asm mov al,ah

/* 这个延时是用来防止两次IO *** 作的最后一次 *** 作的错误,

因为CPU比总线的速度快很多,所以 要延时等待第一次 *** 作完成后再进行第二次 *** 作*/

asm mov cx,1000

delay:

asm loop delay

asm out 42h,al

asm in al,61h

asm mov ah,al

asm or al,3

asm out 61h,al

/* 使用中断15H功能86H延时CX:DX=微秒数*/

asm mov ax,2710h

asm mul bx

asm mov cx,dx

asm mov dx,ax

asm mov ah,86h

asm int 15h /*可用__int__(0x15)代替*/

asm mov al,ah

asm out 61h,al

asm jmp add_count

/*------------------*/

start:

asm mov si,offset mus_frep

asm lea bp,mus_time

frep:

asm mov di,[si]

asm cmp di,-1

asm je end_mus

asm mov bx,[bp]

asm jmp sound

add_count: /*标号不能用汇编语言写*/

asm add si,2

asm add bp,2

asm jmp frep

end_mus:

}

对于上面的程序大家可用伪寄存器的方法写一个,要容易的多!

/*一个发声程序!(引自<<PC技术内幕>>电力版--这个版不好,不如清华版的)*/

#include"dos.h"

main()

{

static union REGS ourregs

outportb(0x43,0xb6)

outportb(0x42,0xee)

outportb(0x42,0)

outportb(0x61,(inportb(0x61)|0x03))

ourregs.h.ah=0x86

ourregs.x.cx=0x001e

ourregs.x.dx=0x8480

int86(0x15,&ourregs,&ourregs)

outportb(0x61,(inportb(0x61)&0xfc))

}

在C程序中使用内联汇编语言

MSPGCC试图在最大程度上和其它MSP430的C工具链兼容,内联汇编语言也要保持兼容是不切实际的。MSPGCC使用通用gcc语法来处理内联汇编语言,用一些扩展来处理MSP430的特殊问题。一开始你会觉得GCC处理内联汇编的方式比其它编译器难使用,然而,这些都是它的有效和强大之处。

内联汇编语法

mspgcc支持GNU标准内联汇慎橘皮编特性‘asm’。在汇编程序里使用‘asm’,你可伍册以用C表达式来指定宽差指令的 *** 作数,这意味着你不用管要使用的数据在寄存器或内存的位置。

你必须指定汇编程序指令模板,如同汇编程序语言一样, *** 作数用引号括起来。例如:

asm("mov %1, %0": "=r" (result): "m" (source))

也可以这样更清晰的写:

asm("mov %src,%res": [res] "=r" (result): [src] "m" (source))

这里‘source’是输入 *** 作数而‘result’是输出 *** 作数,‘=’意味着输出。m 和 r 指示GCC使用的 *** 作数寻址方式。完整的约束符文档在请参照GNU gcc文档。

每个汇编语句用冒号分成四部分。

1. 汇编指令, 定义为 single string 常量:

"mov %src, %res"

2. 一列输出 *** 作数,用逗号隔开。 例子中只包含一个输出 *** 作数,为它定义标志符"res":

[res] "=r" (result)

3. 用逗号分开的输入 *** 作数列表,同样例子中只有一个 *** 作数,为它定义标志符"src":

[src] "m" (source)

4. The clobbered registers. This is left empty in our example, as nothing is clobbered.

译注:不太懂,理解的兄弟请教教我,好像是gcc用来保护寄存器的(保护寄存器使用push,pop就行了)

因此,完整的模式如下:

asm((string asm statement) : [outputs]:[inputs]:[clobbers])

msp430-gcc识别下列约束符:

. m - memory operand( *** 作数).

. I - integer operand.

. r - register operand.

. i - immediate operand (int constants in most cases).

. P - constants, generated by r2 and r3.

gcc支持其它一些处理器通用的约束符。这些约束符促使编译器自动产生前后同步代码、分配寄存器、保存和恢复任何需要的,以确保汇编程序可以被准确高效的处理。例如:

asm("add %[bar],%[foo]"

: [foo] "=r" (foo)

: "[foo]" (foo), [bar] "m" (bar))

等效于:

foo += bar

生成下面的汇编代码:(假设foo为全局变量)

mov &foo, r12

/* #APP */

add &bar, r12

/* #NOAPP */

mov r12, &foo

如果输出 *** 作数没被使用,有必要为‘asm’构造指定‘volatile’。如果你写的头文件要被包含在ANSI C程序中,用‘__asm__’取代‘asm’,‘__volatile__’ 取代 ‘volatile’。

A‘%’后面跟着数字或者已定义的标记强制gcc去取代相关的 *** 作数。对4和8字节的 *** 作数用A,B,C和D修饰来选择 *** 作数中适当的16位。

例如:

#define LONGVAL 0x12345678l

{

long a,b

...

asm("mov %A2, %A0 \n\t"

"mov %B2, %B0 \n\t"

"mov %A2, %A1 \n\t"

"mov %B2, %B1 \n\t"

: "=r"((long)a),"=m"((long)b)

: "i"((long)LONGVAL) )

...

}

or

#define LONGVAL 0x12345678l

{

long a,b

...

asm("mov %A[longval], %A[a] \n\t"

"mov %B[longval], %B[a] \n\t"

"mov %A[longval], %A[b] \n\t"

"mov %B[longval], %B[b] \n\t"

: [a] "=r" ((long) a), [b] "=m" ((long) b)

: [longval] "i"((long) LONGVAL))

...

}

这会产生和下面相类似的汇编语言(假设‘a’在语句块里声明,‘b’为全局的):

...

/* #APP */

mov #llo(305419896), r12

mov #lhi(305419896), r13

mov #llo(305419896), 4(r1) mov #llo(305419896), &b

mov #lhi(305419896), 6(r1) mov #lhi(305419896), &b+2

/* #NOAPP*/

mov r12, 0(r1)

mov r13, 2(r1)

...

由此可以看出:

%A[tag]担当%[tag]的寄存器或地址常量 *** 作数,或者与#llo()一样取整型值。#llo()是汇编宏,取低16位值。

%B[tag]寄存器数加1,地址加2,取 *** 作数的16-31位。

%C[tag]寄存器数加2,地址加4,取32-47位。

%D[tag]寄存器数加3,地址加6,取最后16位。

译注:其中[tag]如果是0,1,2……则:

%0 ... %K 是输出变量

%K+1 ... %N 是输入变量

上例中%0代表a,%1代表b,%2代表LONGVAL。

I,J,K和L修饰符和A,B,C,D修饰符的功能相似,寄存器数加1,地址也是加1。

They should only be used in zero_extendMN operations.

还有 %E 修饰符取代 Rn 从 (mem:xx (reg:xx n)) 作为 @Rn.。对栈顶或指针这是一个有用的修饰符。 !!! 除非很清楚自己在干什么,否则不要使用它!!!

寄存器,变量,标号

既然Gcc不能检查汇编语法,你可以在汇编声明asm()中做任何事,但是请不要。gcc不使用r0,r2,r3,因此,如果你在asm()中使用其中之一作为输出或作为寄存器变量的别名,gcc会用其它的寄存器替代。使用它们作为输入参数会导致"’asm’ operand requires impossible reload"的错误。

在asm()声明中变量能以任何普通方式使用(mind name conversion for [Rr][0-15] names)

Gcc以下面的模式定义标号:

“.Lfe%=”

函数结束标号

“.L__Frame_size_%s”

看上面

“.L%=”

局部标号

“%=”修饰符在文件里代替唯一的数字。

下面的标号为一些扩展的 *** 作数定义:

.Lsren%=

.Lsrcl%=

.Lsre%=

.Lae%=

.Lmsn%=

.Lcsn%=

.Lsend%=

.Lsst%=

.Leaq%=

.LcmpSIe%=

因此,不要使用这些模式。你可以使用其它任何标号。请注意,如果标号以.L开头,表示这是一个局部标号在用msp430-objdump反汇编的输出中。

库调用

在代码生成期间gcc使用一些库函数。这些用非标准的参数传递方案,这些不遵守mspgcc ABI,因此,不要在你的汇编代码中使用,除非你绝对肯定在发生什么。也就是:

乘法:

__mul{qi,hi,si}3

__umul{qi,hi,si}3

__umulsi3hw

对没有硬件乘法器的器件,乘法程序被调用处理相乘。如果目标是处于HI模式(16 bit) or QI 模式 (8 bit),第一个参数r10传递,第二个r12传递,返回值在r14中。在Si模式(32 bit),第一个参数r11:r10,第二个r13:r12,结果在r14:r15中。这两种方式函数返回时输入参数被破坏(clobbered)。

除法:

__divmod{qi,hi,si}4

__udivmod{qi,hi,si}4

如果目标处于 HI 模式 (16 bit) 或 QI 模式 (8 bit), 分子由 r12传递, 分母在r10中。 计算结果 r12/r10的商在r12中余数在r14中。Registers,r10, r11 and r13 are clobbered.

在 SI 模式 (32 bit),分子由r13:r12 传递,分母在r11:r10中。 商保存在r13:r12中,a余数在r15:r14.中。Registers r8, r9, r10, and r11 are cloberred。

所有的浮点数库调用可以以通用方式使用,因为它们遵守mspgcc ABI


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/yw/12368934.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-24
下一篇 2023-05-24

发表评论

登录后才能评论

评论列表(0条)

保存