; This source was made to compile with TASM 3.1 so not all these ; assembler directives apply to every assembler .486p ; for this program we want to enable 32-bit registers like EAX ; use 16-bit operations at the same time though... _TEXT SEGMENT use16 para PUBLIC 'CODE' _TEXT ENDS _DATA SEGMENT use16 para PUBLIC 'DATA' _DATA ENDS _BSS SEGMENT use16 para PUBLIC 'BSS' _BSS ENDS DGROUP GROUP _BSS, _DATA ASSUME CS: _TEXT, DS: DGROUP, SS: DGROUP _TEXT SEGMENT ASSUME CS: _TEXT ; Define our CPUID instruction, which is not supported on TASM 3.1 :-/ ; We put the opcode byte of the CPUID instruction whenever 'CPUID' is called. ; This way we don't need no stinkin' assembler with CPUID instruction support! cpuid macro db 0fh,0a2h ;CPUID opcode endm ; If you want to understand what's going on here you should know how the stack ; works on an OS as well as Intel's stupid segment:offset memory mapping scheme. ; And of course you should have done a little bit of ASM code yourself... ; If not, go read up on those topics. But in a nutshell, your OS will first stack ; the function parameters from last to first, then the return address. Our return ; address is 4 bytes in this case since we compile the C code in Large model (defaults ; to far type pointers), and we define our proc to be a far call in the ASM. ; ; The CPUID instruction takes a parameter, but the 32-bit parameter is stored in EAX ; when the instruction is executed. You can read up on the possible function values ; that can be stored in EAX from Intel's website ; ; void cpuid_query(unsigned long, unsigned long far *) <-- C function prototype ; parameter 1 = CPUID function ; parameter 2 = output buffer for EAX, EBX, ECX, EDX in that order PUBLIC _cpuid_query _cpuid_query proc far ; so before we do anything, stack pointer SP points to end of the return ; address that the C code stacked push bp ; save the current BP, maybe some program needs it later. SP moves up by ; 2 bytes since BP is a 2 byte register mov bp,sp ; move current stack pointer to BP, so now BP = SP = 2+location of ; return address pushad ; save register contents of the general registers pushf ; save the flags, another misc thing thats not necessary for this code push gs di ; save these other registers that we're going to use here ; so our 2nd parameter points to address of the buffer that we want to write the ; CPUID instruction output in. This is a 16-byte buffer (we need to know that ; for sure), so it can store the contents of all four 32-bit registers (4x4bytes) ; ; set seg:offset of destination buffer via 2nd parameter mov eax, [bp+10] ; BP+10 now points to 2+return address(4 bytes)+1st parameter (4 ; bytes), 2+4+4 = 10 right. And this is the location of the end ; of the second parameter (our buffer address). So when we move ; 4 bytes at that address to EAX, we get both the segment and ; offset in the EAX register mov bx, ax ; AX is the lower double byte of EAX, so save that lower dbyte ; to BX. The lower byte happens to contain the buffer address's ; offset because that's just the way DOS stacks the seg:off combo shr eax, 16 ; the upper half of EAX contains the segment, so if we shift ; right 16 bits, the original lower half is shifted out, and we ; get the upper half in the lower half now. mov gs, ax ; same thing as before, save our AX (EAX lower dbyte) into GS mov di, bx ; bring the offset we saved into DI. So GS:DI is now our complete ; segment:offset combo that points to the output buffer! mov eax, [bp+6] ; BP+6 points to 2+return address(4 bytes), which is our 1st ; parameter from the C program. The first parameter is the ; CPUID function ID. Store it in EAX. cpuid ; execute CPUID ; for each register save it to the output buffer, then move DI (our buffer address ; offset) up 4 bytes where the next write is to take place mov dword ptr gs:[di], eax add di,4 mov dword ptr gs:[di], ebx add di,4 mov dword ptr gs:[di], ecx add di,4 mov dword ptr gs:[di], edx pop di gs ; restore registers that we saved in reversed order that was pushed popf popad pop bp ; end restore ret _cpuid_query endp _TEXT ENDS END