What Linux does with IDE? Introduction to Pentium features for trapping reads/writes to memory-locations and i/o-ports Breakpoint Address Registers DR0 DR1 DR2 DR3 Special ‘MOV’ instructions • Use ‘mov DRn, genreg’ to write into DRn • Use ‘mov genreg, DRn’ to read from DRn • These instructions are ‘privileged’ (i.e., can only be executed by code running in ring0) Debug Control Register (DR7) 15 0 0 0 G D 0 0 1 G E L E G 3 L 3 G 2 L 2 G 1 L 1 G 0 Least significant word 31 LEN 3 16 R/W 3 LEN 2 R/W 2 LEN 1 R/W 1 Most significant word LEN 0 R/W 0 L 0 What kinds of breakpoints? LEN LEN 00 = one byte 01 = two bytes 10 = undefined 11 = four bytes R/W R/W 00 = break on instruction fetch only 01 = break on data writes only 10 = break on in/out to port-address ** 11 = break on data reads or writes (but not on instruction fetches) ** Provided the DE-bit (bit 3) is set to 1 in Control Register CR4 Control Register 4 • The Pentium uses Control Register 4 to activate certain extended features of the processor, while still allowing for backward compatibility with systems software that was written for earlier x86 processors • An example: Debug Extensions (DE-bit) 31 CR4 3 other feature bits D E 0 Debug Status Register (DR6) 15 B B T S 0 B D 0 1 1 1 1 1 1 1 1 B 3 B 2 B 1 Least significant word 31 16 unused ( all bits here are set to 1 ) Most significant word B 0 Where to set a breakpoint • Suppose you want to trigger a ‘debug’ fault whenever Linux tries to write/read the IDE Command/Status Register (ioport 0x1F7) • Your debug exception-handler can use the saved CS:EIP values on its stack to check whether an ‘out’ or ‘in’ was just executed • Machine-code: 0xEC for “ in %dx, %al ”, or 0xEE for “ out %al, %dx ” • Could set a ‘breakpoint’ at address EIP-1 Detecting a ‘breakpoint’ • Your debug exception-handler reads DR6 to check for occurrences of breakpoints mov eax, DR6 ; get debug status bt eax, #0 ; breakpoint #0? jnc notBP0 ; no, another cause ; test for other causes… notBP0: The ‘asm’ construct An introduction to the GNU C/C++ compiler’s obscure syntax for doing inline assembly language The ‘asm’ construct • When using C/C++ for systems programs, we sometimes need to employ processorspecific instructions (e.g., to access CPU registers or the current stack area) • Because our high-level languages strive for ‘portability’ across different hardware platforms, these languages don’t provide direct access to CPU registers or stack gcc/g++ extensions • The GNU compilers support an extension to the language which allows us to insert assembler code into our instruction-stream • Operands in registers or global variables can directly appear in assembly language, like this (as can immediate operands): int count = 4; // global variable asm(“ movl count , %eax “); asm(“ imull $5, %eax, %ecx “); Local variables • Variables defined as local to a function are more awkward to reference by name with the ‘asm’ construct, because they reside on the stack and require the generation of offsets from the %ebp register-contents • A special syntax is available for handling such situations in a manner that gcc/g++ can decipher Template • The general construct-format is as follows: asm( instruction-template : output-operand : input-operand : clobber-list ); Example from ‘hdtraps.c’ void trap_handler( unsigned long *tos ) { unsigned long db_status; // … other instructions can go here … asm(“ movl %dr6, %eax “); asm(“ movl %%eax, %0 “ : “=m” (db_status) ); // … other instructions can go here … } In-class exercise • Modify the ‘hdtraps.c’ module so that the output from ‘/proc/hdtraps’ is improved (i.e., more understandable to humans) • Instead of: eax=00530150 opn=EC show: 0x50 = inb( 0x01F7 ); • Instead of: eax=007402EA opn=EE show: outb( 0xEA, 0x01F7 );