I'm taking my first steps in Ada and attempting to write a package which manipulates the CPUID instruction. I found some sequences on the OSDev wiki here for checking the
EFLAGS.ID bit that I'm attempting to modify into an Ada function using inline assembler, but I'm running into a persistent segfault when trying to run it. First, the package spec & body:
-- Specification file: Cpuid.ads -- with Standard_Types; use Standard_Types; package Cpuid is function Is_Supported return Boolean; end Cpuid; -- Body file: Cpuid.adb -- with System.Machine_Code; use System.Machine_Code; package body Cpuid is function Is_Supported return Boolean is HT : constant Character := Character'Val(16#09#); LF : constant Character := Character'Val(16#0A#); EAX : Unsigned_32 := 16#0000_0000#; begin Asm ("pushfq" & LF & HT & "pushfq" & LF & HT & "popq %%rax" & LF & HT & "xorq 0x00200000, %%rax" & LF & HT & "pushq %%rax" & LF & HT & "popfq" & LF & HT & "pushfq" & LF & HT & "popq %%rax" & LF & HT & "xorq (%%rsp), %%rax" & LF & HT & "popfq" & LF & HT & "andq 0x00200000, %%rax" & LF & HT & "movl %%eax, %0", Outputs => Unsigned_32'Asm_Output ("=g", EAX), Volatile => True); if EAX /= 16#0000_0000# then return True; else return False; end if; end Is_Supported; end Cpuid;
Cpuid.Is_Supported function is called by a main program which looks like this:
with Ada.Text_IO; use Ada.Text_IO; with Standard_Types; use Standard_Types; with Cpuid; procedure Cpuid_Check is Some_Int : Unsigned_32 := 0; begin if Cpuid.Is_Supported then Put_Line ("CPUID instruction supported on this CPU."); else Put_Line ("CPUID instruction not supported."); end if; end Cpuid_Check;
Note that the
Standard_Types import is just a spec-only package consisting of handwritten type definitions for
When I attempt to run this program, the program fails with
PROGRAM_ERROR : EXCEPTION_ACCESS_VIOLATION. Using GDB allows me to trace the error to a
SIGSEGV, but I am not able to trace the violation further to a particular instruction issuance. The full debugger output is as follows:
Temporary breakpoint 14, 0x00000000004016e0 in cpuid_check () [program stopped: breakpoint-hit] (gdb) -exec-next Single stepping until exit from function _ada_cpuid_check, which has no line number information. [program running] Program received signal SIGSEGV, Segmentation fault. 0x0000000000401ede in cpuid.is_supported () [program stopped: signal-received] (gdb) -exec-next Single stepping until exit from function cpuid__is_supported, which has no line number information. [program running] Program received signal SIGSEGV, Segmentation fault. 0x00007ffc4d3473f9 in KERNEL32!IsBadReadPtr () from C:\Windows\System32\kernel32.dll [program stopped: signal-received] (gdb)
I've checked to ensure that I'm leaving the stack "as I found it", which, to the best of my ability, appears to be the case - the same number of pushes and pops occur within the inline assembly section. I've also rearranged the assembly sequence into
src, dest format, as my reading on GAS indicates that is the syntax used. In the process, however, I bamboozled myself - I'm more used to Intel/NASM syntax, and GAS looks like a mess in comparison.
Is there a way to rectify this function so that it functions properly?
EDIT: Here is the disassembly dump from GDB, as noted in the comments:
(gdb) disas Dump of assembler code for function cpuid__is_supported: 0x0000000000401ec4 <+0>: push %rbp 0x0000000000401ec5 <+1>: mov %rsp,%rbp 0x0000000000401ec8 <+4>: sub $0x10,%rsp 0x0000000000401ecc <+8>: movb $0x9,-0x1(%rbp) 0x0000000000401ed0 <+12>: movb $0xa,-0x2(%rbp) 0x0000000000401ed4 <+16>: movl $0x0,-0x8(%rbp) 0x0000000000401edb <+23>: pushfq 0x0000000000401edc <+24>: pushfq 0x0000000000401edd <+25>: pop %rax => 0x0000000000401ede <+26>: xor 0x200000,%rax 0x0000000000401ee6 <+34>: push %rax 0x0000000000401ee7 <+35>: popfq 0x0000000000401ee8 <+36>: pushfq 0x0000000000401ee9 <+37>: pop %rax 0x0000000000401eea <+38>: xor (%rsp),%rax 0x0000000000401eee <+42>: popfq 0x0000000000401eef <+43>: and 0x200000,%rax 0x0000000000401ef7 <+51>: mov %eax,%eax 0x0000000000401ef9 <+53>: mov %eax,-0x8(%rbp) 0x0000000000401efc <+56>: cmpl $0x0,-0x8(%rbp) 0x0000000000401f00 <+60>: je 0x401f09 <cpuid__is_supported+69> 0x0000000000401f02 <+62>: mov $0x1,%eax 0x0000000000401f07 <+67>: jmp 0x401f0e <cpuid__is_supported+74> 0x0000000000401f09 <+69>: mov $0x0,%eax 0x0000000000401f0e <+74>: nop 0x0000000000401f0f <+75>: nop 0x0000000000401f10 <+76>: add $0x10,%rsp 0x0000000000401f14 <+80>: pop %rbp 0x0000000000401f15 <+81>: retq 0x0000000000401f16 <+82>: nop 0x0000000000401f17 <+83>: nop 0x0000000000401f18 <+84>: nop 0x0000000000401f19 <+85>: nop 0x0000000000401f1a <+86>: nop 0x0000000000401f1b <+87>: nop 0x0000000000401f1c <+88>: nop 0x0000000000401f1d <+89>: nop 0x0000000000401f1e <+90>: nop 0x0000000000401f1f <+91>: nop End of assembler dump.