博客网 >

WATCOM C/C++ 内嵌式汇编语言指南
作者:分类:默认分类标签:

WATCOM C/C++ 内嵌式汇编语言指南


--------------------------------------------------------------------------------

使用内嵌式汇编需要大量训练和小心的使用,你可以在你的C/C++代码的任何一处使用内嵌式汇编。第一步你需要在你的汇编前面先定义该函数的调用方法,也就是函数说明。Watcom C中所有内嵌式汇编只能是一个独立的函数,必须要声明,是让编译程序能够找到该段函数的起始指针。以便在调用时使用,编译伪指令#pragma aux就是用来干这个的。

请看下面的例子:

    调用DOS调用,返回一个双字节指针,指向DBCS编码表
    Example:
    extern unsigned short far *dbcs_table( void ); //函数原型声明,引号不能少;
    #pragma aux dbcs_table= \                       //编译伪指令引导的汇编代码;
    "mov ax,6300h" \                                //每条指令由引号包住,由\符号结尾;
    "int 21h" \
    value [ds si] \                                 //返回值value被放到[DS:SI]里,这些是编译伪指令,所以不需要引号引住;
    modify [ax];                                    // 一定别忘了通知编译器使用了哪些寄存器,引号不能少;

    产生的机器代码与C语言的关系
    Example:
    #include <stdio.h>
    extern unsigned short far *dbcs_table( void );
    #pragma aux dbcs_table = \
    "mov ax,6300h" \
    "int 21h" \
    value [ds si] \
    modify [ax];
  

void main()                           
{
   if( *dbcs_table() !=0 ) printf( "DBCS supported\n" );      //我们运行的DOS系统支持双字节字符;                                                                                                                                                                                                                                                                           
}

运行这个程序之前,必须考虑一个问题:这个程序不能运行,它既不能在16位模式下运行,也不能在32位保护模式下利用DOS扩展器运行,这是为什么?我们来看一下它的反汇编代码

   0007 b8 00 63      mov ax,6300H
   000a cd 21         int 21H
   000c 83 3c 00      cmp word ptr [si],0000H
   000f 74 0a         je L1
   0011 be 00 00      mov si,offset L2
   0014 56            push si
   0015 e8 00 00      call printf_
   0018 83 c4 02      add sp,0002H

   当DOS中断被调用,DS寄存器已经改变,但是代码发生器没有刷新以前的value,在小内存模式,DS寄存器的内容重来不会改变,(如果要改变,必须保存并在调用完后恢复其原有的值),这是程序员的责任:知道各种内存模式的限制,特别是在使用段寄存器的时候,所以我们必须把上例作一点小小的改动.

   extern unsigned short far *dbcs_table( void );
   #pragma aux dbcs_table = \
   "push ds" \                       //看见了吧,保存DS
   "mov ax,6300h" \
   "int 21h" \
   "mov di,ds" \                     //DS->di
   "pop ds" \                        //恢复DS原有的值
   value [di si] \
   modify [ax];

   这样,在16位模式下,程序就可以运行了,我们来看看反汇编

   0008 1e           push ds
   0009 b8 00 63     mov ax,6300H
   000c cd 21        int 21H
   000e 8c df        mov di,ds
   0010 1f           pop ds
   0011 8e c7        mov es,di
   0013 26 83 3c 00  cmp word ptr es:[si],0000H
   0017 74 0a        je L1
   0019 be 00 00     mov si,offset L2
   001c 56           push si
   001d e8 00 00     call printf_
   0020 83 c4 02     add sp,0002H

   DS寄存器已经被保存.DI值送ES寄存器是为了正确的指向远数据

   这是16位实模式的例子。那么,32位保护模式呢?当使用DOS扩展器,你必须检查它的文档,看它是否支持你想建立的系统调用。这里面有一个原因是有些OS调用不能很明确地返回一个标准16位指针(段:偏移量),而DOS扩展器必须将一个实模式指针转化为32位指针才能正常使用。而即便是DOS4GW也不能支持所有的DOS调用(虽然有其它方法)。每一个发行的DOS扩展程序都综合考虑了使用内嵌式汇编的优缺点。我们将这个问题最后的解决方案举例说明:

Example:       

#ifndef __386__                   //16位模式使用汇编

extern unsigned short far *dbcs_table( void );
#pragma aux dbcs_table = \                                                                                                        "push ds" \
"mov ax,6300h" \
"int 21h" \
"mov di,ds" \
"pop ds" \
value [di si] \
modify [ax];
#else                             //32位模式使用C语言
unsigned short far * dbcs_table( void )

{
  union REGPACK regs;
  static short dbcs_dummy = 0;
  memset(&regs,0,sizeof(regs));
  if(_IsPharLap())                                                                                                                                      

{                                                                                                                     
    PHARLAP_block pblock;
    memset(&pblock,0,sizeof( pblock ));
    pblock.real_eax = 0x6300;                 /* get DBCS vector table */
    pblock.int_num = 0x21;                    /* DOS call */
    regs.x.eax = 0x2511;                      /* issue real-mode interrupt */
    regs.x.edx = FP_OFF(&pblock);             /* DS:EDX -> parameter block */
    regs.w.ds = FP_SEG(&pblock);
    intr(0x21,&regs);
    return(firstmeg(pblock.real_ds,regs.w.si));
  }

else if(_IsDOS4G())

{
DPMI_block dblock;
memset(&dblock,0,sizeof(dblock));
dblock.eax = 0x6300;                     /* get DBCS vector table */
regs.w.ax = 0x300;                       /* DPMI Simulate R-M intr */
regs.h.bl = 0x21;                        /* DOS call */
regs.h.bh = 0;                           /* flags */
regs.w.cx = 0;                           /* # bytes from stack */
regs.x.edi = FP_OFF( &dblock );
regs.x.es = FP_SEG( &dblock );
intr(0x31, &regs );
return(firstmeg( dblock.ds,dblock.esi ) );                                                                                       

}

else  return(&dbcs_dummy);

}

#endif

这样就能在所有的DOS扩展模式下运行。下面是关于区分DOS4GW和其它扩展程序的一个例子

#define REAL_SEGMENT 0x34
void far *firstmeg( unsigned segment, unsigned offset )
{
   void far *meg1;
   if(_IsDOS4G())  meg1 = MK_FP( FP_SEG( &meg1 ), ( segment << 4 ) + offset );
   else  meg1 = MK_FP( REAL_SEGMENT, ( segment << 4 ) + offset );
   return(meg1);

}

    我们已经学习了两条汇编伪指令"modify"和"value"。"modify"描述被内嵌式汇编更改的寄存器,这里你有两个选择,你可以自己来存储/回复被影响的寄存器,那么他们就不需要"modify"说明,但当你请求一个系统调用(如DOS BIOS调用),你必须小心所有被调用改变了的寄存器。如果你没有在"modify"中声明又没有存储和恢复就试图去改变一个寄存器,你会死的很难看。"value"指明哪些寄存器被用作返回值。
   

这里我们给出第三个伪指令,"parm"允许你将C语言的调用参数按顺序放到寄存器里。请看例子:
    Example:

    #include <stdio.h>
    extern void BIOSSetCurPos( unsigned short RowCol,unsigned char Page );
    #pragma aux BIOSSetCurPos = \
    "push bp" \
    "mov ah,2" \
    "int 10h" \
    "pop bp" \
    parm [dx] [bh] \    //RowCol->DX,Page->BH;
    modify [ah];

    void main()
    {
       BIOSSetCurPos( (5 << 8) | 20, 0 );
       printf( "Hello world\n" );
    }

   反汇编

   BIOSSetCurPos( (5 << 8) | 20, 0 );
   0008 ba 14 05      mov dx,0514H
   000b 30 ff         xor bh,bh
   000d 55            push bp
   000e b4 02         mov ah,02H
   0010 cd 10         int 10H
   0012 5d            pop bp

   注意:这就是常说的寄存器传参,不过寄存器太少啦,不够用怎么办?
   虽然例子很短,我们希望你能建立一种概念:
   "parm", "value" , "modify" 被用作:
   1、传递参数放置消息给编译器,通知哪个寄存器被用作放置参数(parm);
   2、传递放置结果寄存器给编译器(value);
   3、告诉编译器那些寄存器被更动了(modify),以便编译器能够自动保存寄存器的值。在我们的一些例子里,我们也可以在我们的代码里自己完成这些工作;
  

内嵌式汇编的标号

内嵌式汇编可以使用标号,这是例子Example:

extern void _disable_video( unsigned );                                                                                          pragma aux _disable_video = \
"again: in al,dx" \                   //这是标号
"test al,8" \
"jz again" \                          //跳转到标号
"mov dx,03c0" \
"mov al,11" \
"out dx,al" \
"mov al,0" \
"out dx,al" \
parm [dx] \
modify [al dx];

内嵌式汇编的变量
我们这里举出一个内嵌式汇编使用变量的例子:
(OK,这是我见过的最好的参数,结果传递方法--全程变量)

Example:

#include <stdio.h>
static short _rowcol;         //这里两个变量是静态变量,不过可以不是
static unsigned char _page;

extern void BIOSSetCurPos( void );
#pragma aux BIOSSetCurPos = \
"mov dx,_rowcol" \            //直接使用全程变量
"mov bh,_page" \
"push bp" \
"mov ah,2" \
"int 10h" \
"pop bp" \
modify [ah bx dx];

void main()
{
   _rowcol = (5 << 8) | 20;
   _page = 0;
   BIOSSetCurPos();
   printf( "Hello world\n" );
}

使用全程变量唯一的规定就是汇编函数必须在变量后面定义
看反汇编:
_rowcol = (5 << 8) | 20;
0008 c7 06 00 00 14 05       mov word ptr __rowcol,0514H
_page = 0;
000e c6 06 00 00 00          mov byte ptr __page,00H
BIOSSetCurPos();
0013 8b 16 00 00             mov dx,__rowcol
0017 8a 3e 00 00             mov bh,__page
001b 55                      push bp
001c b4 02                   mov ah,02H
001e cd 10                   int 10H
0020 5d                      pop bp


下面的例子演示怎么在函数内部使用汇编,一样啦,只要在要使用的变量后面声明就行了。
由此可见:Watcom C的内嵌式汇编与C语言代码的联系并不强烈,把它放到任何地方都没关系,只要通过函数原型声明给编译器一个调用指针,让别的函数能找到它就行了。

Example:

#include <stdio.h>

void main()
{
  short _rowcol;
  unsigned char _page;

  extern void BIOSSetCurPos( void );
  # pragma aux BIOSSetCurPos = \
  "mov dx,_rowcol" \
  "mov bh,_page" \
  "push bp" \
  "mov ah,2" \
  "int 10h" \
  "pop bp" \
  modify [ah bx dx];

  _rowcol = (5 << 8) | 20;
  _page = 0;
  BIOSSetCurPos();
  printf( "Hello world\n" );
}

反汇编
_rowcol = (5 << 8) | 20;
000e c7 46 fc 14 05      mov word ptr -4H[bp],0514H
_page = 0;
0013 c6 46 fe 00         mov byte ptr -2H[bp],00H
BIOSSetCurPos();
0017 8b 96 fc ff         mov dx,-4H[bp]
001b 8a be fe ff         mov bh,-2H[bp]
001f 55                  push bp
0020 b4 02               mov ah,02H
0022 cd 10               int 10H
0024 5d                  pop bp

不过注意,这种把汇编放到函数内部的方法可能要受编译器优化的影响,因此不推荐使用。
         

<< vc go to the... / pragma指令简介 >>

专题推荐

不平凡的水果世界

不平凡的水果世界

平凡的水果世界,平凡中的不平凡。 今朝看水果是水果 ,看水果还是水果 ,看水果已不是水果。这境界,谁人可比?在不平凡的水果世界里,仁者见仁,智者见智。

中国春节的那些习俗

中国春节的那些习俗

正月是农历新年的开始,人们往往将它看作是新的一年年运好坏的兆示期。所以,过年的时候“禁忌”特别多。当然,各个地方的风俗习惯不一样,过年的禁忌也是不一样的。

评论
0/200
表情 验证码:

Kitty

  • 文章总数0
  • 画报总数0
  • 画报点击数0
  • 文章点击数0
个人排行
        博文分类
        日期归档