• Please review our updated Terms and Rules here

Detecting if current CPU is 486 or newer from within Borland Turbo C++ 3.0?

juj

Member
Joined
Jan 25, 2022
Messages
40
Hi, I'm trying to write Borland Turbo C++ 3.0 compatible code to detect if the current CPU is a 486 or newer (to be able to distinguish against 386 or older).

In https://www.rcollins.org/ddj/Sep96/Sep96.html I find the relevant assembly code, but given that Turbo C++ 3.0 is such an old compiler, I can't get listings three and four to work. (PUSHFD, POPFD and EAX/EBX/ECX registers are not recognized)

I was thinking to encapsulate the problem by authoring a function get_EFLAGS(), which would just return the EFLAGS register in a 32-bit variable.

Based on these two links that I found:
- https://faydoc.tripod.com/cpu/pushfd.htm
- https://stackoverflow.com/questions...operand-size-in-the-x86-64-amd64-architecture

I got an impression that I might be able to execute PUSHFD, and access EAX/EBX/ECX registers via an operand-size prefix?

So my attempt was something like this:

Code:
unsigned long get_EFLAGS()
{
  asm {
    db 0x67
    pushf    // Would a prefix 67h convert a pushf to a pushfd?
    db 0x67
    pop ax   // Would a prefix 67h convert a pop ax to a pop eax?
  
    mov dx, ax // Turbo C++ 3.0 return 32-bit quantities in DX:AX, so save low part in DX.

    db 0x67    // Use prefix 67h to convert a 'shr ax, 16' to a 'shr eax, 16'?
    db 0xC1
    db 0xE8
    db 0x10    // shr ax, 16
  
    ret
  }
}

But this doesn't quite work, and I'm not sure how to troubleshoot. It returns a 00000000h value always.

My thinking was that if I got this get_EFLAGS() to work, and then a similar set_EFLAGS() function, I could then use those to recreate the high level test logic for listings 1-4 in the linked DDJ article above. Any tips on how to implement such functions from Turbo C++ 3.0 real mode would be much appreciated. Thanks!
 
0x67 is the address size override. You want 0x66 (operand size) instead. Also you got the low and high word wrong.

Code:
    db 0x66
    pushf    // pushfd
    db 0x66
    pop dx   // pop edx

    mov ax, dx // return low part in AX

    db 0x66
    shr dx, 16 // shr edx, 16; return high part in DX

The "ret" at the end is probably not needed, or may even cause problems since it's inside a C function that may have it's own epilogue code.
 
Turbo C++ 1.0, which regarding real mode code is not very different to BC 3.0, still allows assembly meta commands, so you could write:

Code:
asm .386
asm pushfd
asm pop        eax

and it should compile right. It's a pitty Borland removed this feature from later versions as it may be useful sometimes. This is one of the reasons I still use TC++ 1.0.
 
Thanks for the help. I got now up to the following:

C++:
unsigned int get_flags()
{
    unsigned int flags;
    asm {
        pushf
        pop ax
        mov flags, ax
    }
    return flags;
}

void set_flags(unsigned int flags)
{
    asm {
        mov ax, flags
        push ax
        popf
    }
}

unsigned long get_eflags()
{
    unsigned long eflags;
    asm {
        db 0x66, 0x9C // PUSHFD
        pop ax
        mov word ptr [eflags], ax
        pop ax
        mov word ptr [eflags+2], ax
    }
    return eflags;
}

void set_eflags(unsigned long eflags)
{
    asm {
        mov ax, word ptr [eflags+2]
        push ax
        mov ax, word ptr [eflags]
        push ax
        db 0x66, 0x9D // POPFD
    }
}

int is_8088()
{
    unsigned int orig = get_flags();
    set_flags(orig & 0x0FFF); // Try clearing bits 12-15 in FLAGS..
    unsigned long toggled = get_flags();
    set_flags(orig);
    return (toggled & 0xF000) == 0xF000; // If bits 12-15 stuck at 1, it is a 8088.
}

int is_286()
{
    unsigned int orig = get_flags();
    set_flags(orig | 0xF000); // Try setting bits 12-15 in FLAGS..
    unsigned long toggled = get_flags();
    set_flags(orig);
    return (toggled & 0xF000) == 0x0000; // If bits 12-15 stuck at 0, it is a 286.
}

int is_486_or_newer()
{
    if (is_8088() || is_286()) return 0; // If we have <= 286, can't access EFLAGS.
    unsigned long orig = get_eflags();
    set_eflags(orig ^ 0x40000ul); // Try flipping bit 18 (AC), which only works on 486+
    unsigned long toggled = get_eflags();
    set_eflags(orig);
    return ((orig ^ toggled) & 0x40000ul) != 0; // If we couldn't flip bit 18, must be a 386.
}

const char *cpu()
{
    if (is_8088()) return "8086/8088";
    if (is_286()) return "286";
    return is_486_or_newer() ? "486 or newer" : "386";
}

Haven't tested yet on all CPU types, though hopefully didn't do any mistakes.
 
I highly recommend not mixing ASM and C this way. You are testing for CPU side-effects without telling the compiler, and hoping that it accidentally generates stupid code which does not interfere.

My recommendation would be to write each detection sequence in ASM, then call them from C code.
 
It can work, but you don't know what code the compiler will generate around it. If you do it using inline assembly like this, it always makes sense to go into Turbo Debugger and View -> CPU so you can see the instructions the compiler used and see if it works the way you want it to.
 
Back
Top