-2

I have written a macro in Turbo C++

Part C++ program (macro only)

/* @intvar address=0x8f79fff4 */

{

   -asm mov cx,08f7h
   _asm mov es,cx

   -asm mov ax,9000h
   -asm mov ds,ax

   -asm mov ax,0fff4h
   -asm mov si,ax

/* -asm mov cl,0fh */  

   -asm mov eax,es
   -asm shl,eax,1
   -asm shl,eax,1
   -asm shl,eax,1
   -asm shl,eax,1
   -asm shl,eax,1
   -asm shl,eax,1
   -asm shl,eax,1
   -asm shl,eax,1
   -asm shl,eax,1
   -asm shl,eax,1
   -asm shl,eax,1
   -asm shl,eax,1
   -asm shl,eax,1
   -asm shl,eax,1
   -asm shl,eax,1
   -asm shl,eax,1

   -asm add eax,ds[si]

   -asm mov ah,9h
   -asm mov al,[eax]

   -asm mov bh,0h
   -asm mov bl,0h
   -asm mov cx,0h

   -asm int 10h
}
  • The line -asm mov cl,0fh causes a syntax error.

    • Hence my use of 16 off -asm shl,eax,1.
  • The line -asm mov eax,es causes an error "eax not recognised"

  • The TASM directive .386P to use the 32-bit register eax won't work

Why can't I write 0fh to cl register?

Peter Cordes
  • 245,674
  • 35
  • 423
  • 606
James
  • 1
  • 2
  • 6
    Why are you using TurboC++? It's decades old and doesn't support modern C++ features introduced in the past 20 years... – Dai Mar 04 '21 at 02:26
  • 1
    I'm not a TurboC++ user, but I noticed you have `_asm` on line 2 but `-asm` on line 1 and after line 3. Is there a difference? – Dai Mar 04 '21 at 02:28
  • I think it is supposed to be `_asm` however I have not used Turbo C++ since the 1990s. – drescherjm Mar 04 '21 at 02:43
  • 1
    Use `shl eax, 16` like a normal person: shift by immediate was new in 186, so code that uses 32-bit registers automatically implies you can use immediate shifts. – Peter Cordes Mar 04 '21 at 04:08
  • Also, note that you destroy the 2nd byte of EAX `mov ah,9h` *before* you dereference it with `mov al,[eax]`. Also, if you're trying to do an `es:something` address calculation, note that in real mode, segment register values are only shifted left by 4 to get the segment base, not 16. IDK, maybe you're keeping something else in ES? – Peter Cordes Mar 04 '21 at 04:16
  • Some assemblers may not allow `mov eax, es`, maybe only `mov ax, es`. See https://www.felixcloutier.com/x86/mov re: what happens with 32-bit operand-size, and Intel's comments on what some assemblers do in terms of inserting operand-size prefixes or not. – Peter Cordes Mar 04 '21 at 04:18

1 Answers1

3

I never used _asm nor -asm (and I think the -asm is invalid) instead I use this:

asm{  

   // multi line 
   // assembly code
   // in here 
   };

This works without problems also in old turbo C++ and Pascal ... so simply get rid of all the -asm and _asm and just place single asm{ at start and } at the end of your asm code.

However your code has many syntax errors !!!

for example what the heck is:

shl,eax,1

It should be:

shl eax,1

Or better, you can use a shl eax,16 instruction. Immediate shifts were new in 186, and code that uses 32-bit registers can only run on a 386 or newer. Or use 2x 16bit push and 1x 32-bit pop to move es to higher 16 bit of eax, as one way to concatenate two 16-bit things into a 32-bit register. (Fairly slow on modern CPUs, though.)

Another problem is that Turbo C++ compiler can not generate code with 32 bit registers, even in 16-bit mode (at least mine). So you'd really need to use a different compiler for any of that to be possible.

In the project option you can check x386 and x387 instruction set but that does not apply for inline assembly ... The lines with 32 bit registers will throw an error (unknown eax or something like that) So you need to rewrite your code to use 16 bit registers.


Another problem is you use wrong hex number format.

In C/C++ you need to use 0x0 prefix instead of h suffix. So for example 9000h should be 0x09000 !!!

Another possible problem is addressing You use

add eax,ds[si]

You're using 16-bit pointers here, but later use a 32-bit [eax] pointer. Are you sure that's what you want? Also, did you really want to do a 32-bit load, instead of just merging a new low 16 bits into EAX with mov ax, [si]? The difference would be if a carry-out from the add propagated into the upper bits, or if the upper half of the dword at that memory location is non-zero.

Also, DS is the default segment (except for BP or ESP), so why specify DS explicitly here but not in [eax]? You can't avoid the DS base with a 32-bit offset. Also the format is like this:

add ax,[ds:si]

or:

add eax,dword[ds:si]

Depending on what you want to do. The format of addressing is different among asm compilers (TASM,NASM,...) which was a pain in the ass.

Also, asm mov ah,9 modifies the 2nd byte of EAX right before you try to dereference EAX as a pointer with mov al,[eax]. Doing those two instructions in the other order could make sense, if you were constructing a valid offset (i.e. low part of a seg:off address) in EAX.


And lastly but not least YOU DO NOT PRESERVE REGISTER VALUES that is simply a bad idea. Unlike MSVC where the compiler figures out which registers your asm uses and saves/restores them for you, you have to add push/pop statements to start/end of your asm code otherwise you risk corrupting the register values the C/C++ compiler-generated code around your block was depending on.

Not all registers need to be preserved (unless you code ISR) but segment and index registers should be restored. IIRC ax holds the return value of asm functions (not sure if in TC too).

(In MSVC you can leave a value in EAX and then let execution fall off the end of a non-void function (without a return), and the function's return value is what you left in EAX. MSVC actually supports this when inlining functions containing an asm{} block, but if this works in TC it's probably just by not inlining and not messing up AX after asm blocks.)


Also take a look at this:

In the first bullets there are examples for TC++ using asm ...

Here another example (using x387) I just found in my TC archives:

long sin(float a,long b)
    {
    long alfa=long(a*1000);
    long far* ptr=(long far*)0xA000FA00;
    ptr[0]=alfa;
    ptr[1]=1000;
    ptr[2]=b;
    asm {
        fninit
        push    es
        push    di
        mov di,0xA000
        mov es,di
        mov di,0xFA00
        fild    dword[es:di]
        fild    dword[es:di+4]
        fdivp   st(1),st(0)
        db  0xD9,0xFE
        fild    dword[es:di+8]
        fmulp   st(1),st(0);
        fistp   dword[es:di]
        pop di
        pop es
        }
    b=ptr[0];
    return b;
    }

from which I infer the addressing syntax/format of TC as I simply did not remember anymore.

Peter Cordes
  • 245,674
  • 35
  • 423
  • 606
Spektre
  • 41,942
  • 8
  • 91
  • 312
  • 1
    `mov eax,es` isn't wrong; good assemblers accept that. As Intel documents (https://www.felixcloutier.com/x86/mov), it does "Move zero extended 16-bit segment register to r16/r32/r64/m16" (depending on the destination operand). Except that on CPUs before Pentium Pro, the upper bits of the GP register are undefined, not zeroed. If you care about those old CPUs, probably want to avoid an operand-size prefix and write `mov ax, es` since this is 16-bit code, before left shifting EAX which will discard high garbage anyway. – Peter Cordes Mar 04 '21 at 09:40
  • Yes, `shl eax,16` is the way to go (and well spotted on the extra comma). Immediate shifts were new in 186, so code that requires 386 for 32-bit registers can safely assume that, too. I don't understand the point of the OP's `add eax,ds[si]` after the shift; it's adding some 32-bit value that might affect or carry into the upper bits? If it's really trying to form a seg:off pair, `mov ax, [si]` would make more sense, but it makes no sense to deref that seg:off as an offset, unless ES held something weird. – Peter Cordes Mar 04 '21 at 09:47
  • DS is the default segment for all base regs except [e/bp] or [esp]; your comment about [eax] is also spurious. It's probably wrong given how the value was constructed, though. (and definitely wrong since `mov ah, 9` destroys one byte of EAX first!) Also, is `ds[si]` the right syntax? Normally MASM/TASM is `ds:[si]`, so would `ds[si]` look for a symbol/label called `ds`? – Peter Cordes Mar 04 '21 at 09:49
  • Re: preserving registers: in MSVC, you don't need to preserve registers. The compiler will save anything it needs to around your asm statement. (Yes, this means the compiler has to know which registers are affected by every possible x86 instruction, including implicit uses like `lodsd`, `mul`, or `popa` (this is one reason why GNU C and Rust inline asm *doesn't* do this for you; compilers would need to parse this domain-specific language for every target ISA they support). So anyway doing your own push/pop is a total waste of instructions, in MSVC. Is TC different? – Peter Cordes Mar 04 '21 at 09:52
  • @PeterCordes IIRC it depends if it is `asm` function or not and if not what is around the asm code ... but safer is to presume not (had many corruption in my old 3D gfx engine before i found out)... IIRC especially dangerous is `es,ds,bp,si,di` and sometimes even `dx` – Spektre Mar 04 '21 at 09:54
  • *Turbo C++ compiler can not generate 32 bit asm code* - Not sure why you're mentioning this. This is clearly 16-bit code, given the use of a 16-bit address (`[si]`) and of a 16-bit-only BIOS `int 0x10` call. Probably for real-mode, with a major misunderstanding of how real-mode segmentation works (`base << 4 + offset`, not `base << 16 + offset`), if I'm guessing right about the shift/add. If it's protected mode (32 or 16), getting a value out of ES to use as the high part of an offset is even more weird because it couldn't hold arbitrary integers. – Peter Cordes Mar 04 '21 at 09:55
  • Oh, TC doesn't support code-gen with 32-bit registers at all? Yeah that would be a big problem! (Or not, if what the OP was trying to do can't work anyway :P) – Peter Cordes Mar 04 '21 at 09:57
  • Ok, yeah it makes sense that you'd have to preserve `es` and `ds`, and maybe `bp`, but if other registers don't get saved automatically for you, then yeah TC is different from MSVC. MSVC taking care of registers lets it inline functions that contain `asm` blocks, unlike what you're describing where the fact that it's in a non-inline function let you clobber AX and CX at least. (Normally also DX would also be call-clobbered, at least EDX is in normal 32-bit calling conventions.) – Peter Cordes Mar 04 '21 at 09:59
  • In your x87 example, I think you could save some instructions by using `fidiv` and `fimul` instead of `fild` / `fdivp`. Also, `fninit` is slow, and if you (and the compiler) properly balance the FPU stack it's not needed. Also, I'd comment the `0xD9,0xFE` to note that it's 387 `fsin` https://www.felixcloutier.com/x86/fsin – Peter Cordes Mar 04 '21 at 10:00
  • @PeterCordes that was coded ages ago when I was too rookie with FPU ... but the fninit was necessary due to some compiler bug corrupting FPU stack and state (IIRC it was related to the missing push/pop for some registers in unrelated CPU only code) ... also missing `return` in function can do this on borland compilers BCC32 included – Spektre Mar 04 '21 at 10:03
  • 1
    I edited your answer to remove objections to things that aren't definitely wrong; there's enough here to complain about that's definitely wrong :P. e.g. `add eax, [si]` looks totally normal to me for working with 32-bit data in 16-bit code. The problem (I think, if any) is the overall logic of what 16 and 32-bit pointers (offsets) are, not that mixing of 32-bit operand-size and 16-bit address-size. Those two things are separate attributes of an instruction and are overridden to the non-default for the current mode by separate prefixes (`66` and `67` respectively). – Peter Cordes Mar 04 '21 at 10:21
  • @PeterCordes thx ... looks I am not the only one doing multi edit corrections :) ... maybe we should clear up some comments – Spektre Mar 04 '21 at 10:23
  • I think the comment thread is fine; I explained why each thing was true in a way that might even be useful to beginners learning some facts about x86, and I don't expect this Q&A to get a lot of future readers. Or else some mod will "helpfully" move it to chat for us :/ – Peter Cordes Mar 04 '21 at 10:27
  • 1
    Early version of Turbo-C didn't allow multiline ASM statements so using `_asm` on each line without curly braces was needed. – Michael Petch Mar 04 '21 at 10:49