📄 cpasm.gml
字号:
.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( ®s, 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, ®s );
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, ®s );
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 + -