⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 cpasm.gml

📁 开放源码的编译器open watcom 1.6.0版的源代码
💻 GML
📖 第 1 页 / 共 2 页
字号:
.chap *refid=cpasm In-line Assembly Language
.*
.np
.ix 'in-line assembly language'
.ix 'assembly language' 'in-line'
The chapters entitled :HDREF refid='prg86'. and :HDREF refid='prg386'.
briefly describe the use of the auxiliary pragma to create a sequence
of assembly language instructions that can be placed anywhere
executable C/C++ statements can appear in your source code.
This chapter is devoted to an in-depth look at in-line assembly
language programming.
.np
The reasons for resorting to in-line assembly code are varied:
.begbull
.bull
Speed - You may be interested in optimizing a heavily-used section of
code.
.bull
Size - You may wish to optimize a module for size by replacing a
library function call with a direct system call.
.bull
Architecture - You may want to access certain features of the Intel
x86 architecture that cannot be done so with C/C++ statements.
.endbull
.np
There are also some reasons for not resorting to in-line assembly code.
.begbull
.bull
Portability - The code is not portable to different architectures.
.bull
Optimization - Sometimes an optimizing compiler can do a better job of
arranging the instruction stream so that it is optimal for a
particular processor (such as the 486 or Pentium).
.endbull
.*
.section *refid=inlinenv In-line Assembly Language Default Environment
.*
.np
In next table is description of the default in-line assembler 
environment in dependency on C/C++ compilers CPU switch for x86 target
platform.
..sk 1
..im inlintab
.pc
This environment can be simply changed by appropriate directives.
.pc
.us Note:
.pc
This change is valid only for the block of assembly source code.
After this block, default setting is restored.
.*
.section In-line Assembly Language Tutorial
.*
.np
Doing in-line assembly is reasonably straight-forward with &product.
although care must be exercised.
You can generate a sequence of in-line assembly anywhere in your C/C++
code stream.
The first step is to define the sequence of instructions that you wish
to place in-line.
The auxiliary pragma is used to do this.
Here is a simple example based on a DOS function call that returns a
far pointer to the Double-Byte Character Set (DBCS) encoding table.
.exam begin
extern unsigned short far *dbcs_table( void );
#pragma aux dbcs_table = \
        "mov ax,6300h"   \
        "int 21h"        \
        value   [ds si]  \
        modify  [ax];
.exam end
.np
To set up the DOS call, the AH register must contain the hexadecimal
value "63" (63h).
A DOS function call is invoked by interrupt 21h.
DOS returns a far pointer in DS:SI to a table of byte pairs in the
form (start of range, end of range).
On a non-DBCS system, the first pair will be (0,0).
On a Japanese DBCS system, the first pair will be (81h,9Fh).
.np
With each pragma, we define a corresponding function prototype that
explains the behaviour of the function in terms of C/C++.
Essentially, it is a function that does not take any arguments and
that returns a far pointer to a unsigned short item.
.np
The pragma indicates that the result of this "function" is returned in
DS:SI (value [ds si]).
The pragma also indicates that the AX register is modified by the sequence
of in-line assembly code (modify [ax]).
.np
Having defined our in-line assembly code, let us see how it is used
in actual C code.
.exam begin
#include <stdio.h>

extern unsigned short far *dbcs_table( void );
#pragma aux dbcs_table = \
        "mov ax,6300h"   \
        "int 21h"        \
        value   [ds si]  \
        modify  [ax];
.exam break

void main()
{
    if( *dbcs_table() != 0 ) {
        /*
            we are running on a DOS system that
            supports double-byte characters
        */
        printf( "DBCS supported\n" );
    }
}
.exam end
.np
Before you attempt to compile and run this example, consider this:
The program will not work!  At least, it will not work in most
16-bit memory models.
And it doesn't work at all in 32-bit protected mode using a DOS
extender.
What is wrong with it?
.np
We can examine the disassembled code for this program in order to see
why it does not always work in 16-bit real-mode applications.
.code begin
    if( *dbcs_table() != 0 ) {
        /*
            we are running on a DOS system that
            supports double-byte characters
        */
 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

        printf( "DBCS supported\n" );
    }
 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

}
.code end
.np
After the DOS interrupt call, the DS register has been altered and the
code generator does nothing to recover the previous value.
In the small memory model, the contents of the DS register never
change (and any code that causes a change to DS must save and restore
its value).
It is the programmer's responsibility to be aware of the restrictions
imposed by certain memory models especially with regards to the use of
segmentation registers.
So we must make a small change to the pragma.
.millust begin
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];
.millust end
.np
If we compile and run this example with a 16-bit compiler, it
will work properly.
We can examine the disassembled code for this revised program.
.code begin
    if( *dbcs_table() != 0 ) {
        /*
            we are running on a DOS system that
            supports double-byte characters
        */
 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

        printf( "DBCS supported\n" );
    }
 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
.code end
.np
If you examine this code, you can see that the DS register is saved
and restored by the in-line assembly code.
The code generator, having been informed that the far pointer is
returned in (DI:SI), loads up the ES register from DI in order to
reference the far data correctly.
.np
That takes care of the 16-bit real-mode case.
What about 32-bit protected mode?
When using a DOS extender, you must examine the accompanying
documentation to see if the system call that you wish to make is
supported by the DOS extender.
One of the reasons that this particular DOS call is not so clear-cut
is that it returns a 16-bit real-mode segment:offset pointer.
A real-mode pointer must be converted by the DOS extender into a
protected-mode pointer in order to make it useful.
As it turns out, neither the Tenberry Software DOS/4G(W) nor Phar Lap
DOS extenders support this particular DOS call (although others may).
The issues with each DOS extender are complex enough that the relative
merits of using in-line assembly code are not worth it.
We present an excerpt from the final solution to this problem.
.ix 'Phar Lap example'
.ix 'DOS/4GW example'
.ix 'DPMI example'
.tinyexam begin
#ifndef __386__

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];
.tinyexam break
#else

unsigned short far * dbcs_table( void )
{
    union REGPACK       regs;
    static short        dbcs_dummy = 0;

    memset( &regs, 0, sizeof( regs ) );
.tinyexam break
    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 ) );
.tinyexam break
    } 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 ) );
.tinyexam break
    } else {
        return( &dbcs_dummy );
    }
}

#endif
.tinyexam end
.np
The 16-bit version will use in-line assembly code but the 32-bit
version will use a C function that has been crafted to work with both
Tenberry Software DOS/4G(W) and Phar Lap DOS extenders.
The
.id firstmeg
function used in the example is shown below.
.ix 'memory' 'first megabyte'
.ix 'real-mode memory'
.code begin
#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 );
}
.code end
.np
We have taken a brief look at two features of the auxiliary pragma,
the "modify" and "value" attributes.
.np
The "modify" attribute describes those registers that are modified by
the execution of the sequence of in-line code.
You usually have two choices here; you can save/restore registers that
are affected by the code sequence in which case they need not appear
in the modify list or you can let the code generator handle the fact
that the registers are modified by the code sequence.
When you invoke a system function (such as a DOS or BIOS call), you
should be careful about any side effects that the call has on
registers.
If a register is modified by a call and you have not listed it in the
modify list or saved/restored it, this can have a disastrous affect on
the rest of the code in the function where you are including the
in-line code.
.np
The "value" attribute describes the register or registers in
which a value is returned (we use the term "returned", not in the
sense that a function returns a value, but in the sense that a
result is available after execution of the code sequence).
.np
This leads the discussion into the third feature of the auxiliary
pragma, the feature that allows us to place the results of C
expressions into specific registers as part of the "setup"
for the sequence of in-line code.
To illustrate this, let us look at another example.
.exam begin
extern void BIOSSetCurPos( unsigned short __rowcol,
                           unsigned char __page );
#pragma aux BIOSSetCurPos =     \
        "push bp"               \
        "mov ah,2"              \
        "int 10h"               \
        "pop bp"                \
        parm   [dx] [bh]        \
        modify [ah];
.exam end
.np
The "parm" attribute specifies the list of registers into which values
are to be placed as part of the prologue to the in-line code sequence.
In the above example, the "set cursor position" function requires
three pieces of information.
It requires that the cursor row value be placed in the DH register,
that the cursor column value be placed in the DL register, and that
the screen page number be placed in the BH register.
In this example, we have decided to combine the row and column
information into a single "argument" to the function.
Note that the function prototype for
.id BIOSSetCurPos
is important.
It describes the types and number of arguments to be set up for the
in-line code.
It also describes the type of the return value (in this case there is
none).
.np
Once again, having defined our in-line assembly code, let us see how
it is used in actual C code.
.exam begin
#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]        \
        modify [ah];
.exam break

void main()
{
    BIOSSetCurPos( (5 << 8) | 20, 0 );
    printf( "Hello world\n" );
}
.exam end
.np
To see how the code generator set up the register values for the
in-line code, let us take a look at the disassembled code.
.code begin
    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
.code end
.pc
As we expected, the result of the expression for the row and column is
placed in the DX register and the page number is placed in the BH
register.
The remaining instructions are our in-line code sequence.
.np
Although our examples have been simple, you should be able to
generalize them to your situation.
.np

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -