fdbg.uefi.x64.0005
uefi asm level debugger for EFI_IMAGE_MACHINE_x64
author: Feryno


Any questions, improvements, found misbehaviour, ... please report them into FASM forum, section Projects and ideas, thread fdbg


----------------------------------------------------------------


version 0006
2023-sep-24
[+] command for setting ioport 80h to desired value (hexadecimal display on some MBs, under ASUS with name Q-code, then ASROCK rack mount / server MBs)

version 0005
2019-jan-06
[+] executable arguments

version 0004
2015-jul-22
[-] fixed bug in a00.asm when accessing stack frame

version 0003
2013-jan-05
[-] fixed bug in hexa.asm for converting dword to hexa ascii string

version 0002
2012-sep-03
[+] rewritten code for loading executable
[+] added choice of GPR64 and RIP as input on some commands (e.g. c rip-40, d rsi, e rsp+20, b rip+6, m d [rax+10]=rcx-ba0)
[+] support for most important symbols as defined in UEFI spec
[+] kill command


version 0001
2012-jul-22
[+] you can type the executable name (no need to compile your program with fdbg engine as in version 0000 anymore)


version 0000
2012-mar-11
[ ] initial version
[-] necessity to compile your FASM source code with the fdbg engine into 1 executable
[+] enabling features (if present) as YMM registers, debug control (for AMD as well various Intel models)
[!] warning !!! you can't debug some UEFI system calls which are used by fdbg, e.g. you can't put breakpoint inside OutputString !!! either WaitForKey !!! this can be solved if fdbg writes pixels directly into video card memory framebuffer and watches keyboard I/O port (hypervisor may do it silently and transparently for UEFI)


----------------------------------------------------------------


you can run fdbg from UEFI shell
or you can copy fdbg to USB flash drive with FAT filesystem into \efi\boot\bootx64.efi and boot from the flash drive under UEFI mode (booting drive using BIOS emulation never loads you program, but loads bootsector of the drive)

when launching fdbg from UEFI shell you can pass arguments in a form [path]executable[.efi] arguments_for_debuggee, e.g.
fdbg \path\program.efi argument_for_program
if you start fdbg without arguments, fdbg prompts you to type the path and name of your program and arguments passed to your program
the executable must be in the same device and partition as fdbg and you must type full path with executable name, if you type only executable name without path, fdbg tries to load it from root, e.g. program.efi is loaded from \program.efi, if you type path without beginning \ the path is taken from the root e.g. asm\program.efi is loaded from \asm\program.efi
here samples (sample 0 and 1 are the same, 2 and 3 are equivalent)
test.efi
\test.efi
asm\prog\test.efi
\asm\prog\test.efi
test argument0
test arg0 arg1
the limit for executable and arguments size is msg_clt_size defined in src\cli.inc
then fdbg tries to load the file you entered (fdbg exits if that fails, e.g. typo error, file doesn't exist, file is not uefi x64 executable), then fdbg determines its base and entrypoint, writes breakpoint at entrypoint, starts the executable, then the executable hits breakpoint which is captured by fdbg and the executable is halted at its entry point
then fdbg waits for commands
these commands are implemented:
h
q
g
k
s
t
u
v
w
r
x
y
b
a
c
d
e
m


[00]
command h prints help

if you want to print help about command r then the syntax is:
h r

if you want to know syntax/usage of m command, then do this:
h m


[01]
q command erases all breakpoints you put into program (during debugging) and runs it


[02]
g command runs the programm


[03]
k command kills the program, use it only in extreme situations, see Exit() in Boot Services


[04]
s command executes single step (sets RFLAGS.TF=1 and runs the program, then the execution of the first instruction causes #DB and the program is halted after executing the first instruction/its first iteration)


[05]
t command executes trace over for instructions CALL, LOOP, REP (for all other instructions it falls back into command s single step)
N.B. it tries to use hardware breakpoint (debug registers), if all 4 are used then it uses software breakpoint (writing int3 breakpoint into memory)


[06]
u command tries to exctract (from the stack) the return address from procedure, then puts breakpoint at that address and runs the program, so the program should stop after returning from procedure (if something else doesn't halt it earlier)


[07]
v command does single step on branching instruction (sets DebugCtl.BTF and does the same as the command s, refer to the AMD/Intel manuals or search internet for this feature if you don't know it yet)


[08]
w command prints last branch from/to MSRs registers (fdbg always enables DebugCtl.LBR and captures branches)
on AMD there are 1 pair last branch from/to and 1 pair last exception from/to
when an exception occures (as debug, breakpoint, etc), last branch from/to are copied to last exception from/to, last branch from holds the address causing exception (debug, breakpoint, bug) and is the same as the value of RIP register, last branch to is useless because holds the address of exception handler, last exception from/to holds usefull values of branching instructions which occured before exception (debug, breakpoint, bug)
on Intel, there are upto 16 pairs last branch from/to
Because there are too many Intel CPU models with 3 different settings of MSRs for LBF/LBT/TOS, I'll developed some scanning method for determining these MSRs (I don't have time to implement checking of hundreds various CPU models and assign correct MSRs to them and update the list every time intel releases new model, so I went different way and simple scanned MSRs whether they exist)
I'll be extremely happy if the detection passes on Intel CPUs and finds correct MSRs (can't test that at every intel cpu model, hope that implemented procedures are stable and work correctly)


[09]
r command prints/changes register(s)
bare command r without any param prints 16 GPRs, RIP, RFLAGS
r
if you want to print only 1 register, then use commands like:
r rax
r rip
r st7
r mm5
r xmm14
r xmm15
if you want to change register then use commands like:
r rax=0
r r15=FFFFFFFFFFFFFFFF
r rip=rip-2
r rbp=rsp+80
r rax=rax+eeeeeeeeeeeeeeee
r zf=1
r cf=0
r st0=2.578
r ymm15=5.1 -2.4 7.8 1*10^7 -5*10^-8 0.2 -0.4 4000.
for more samples type this command:
h r
N.B. for floating points, fdbg calculates the count of decimal dots and decides whether you want to set single precison / double precision / hexa value into register


[0A]
x command shows / sets hardware breakpoint(s) = breakpoint(s) using debug registers (they don't change memory, they use only registers, but there are only 4 registers available)
without params it shows hardware breakpoints:
x
if there isn't any then it shows nothing of course
to set hw bp use commands like
x p e 265152
x t e rip+1f
x p r b 266015
x t w w 40201a
x t w q 402018
x t r d 402034
x t i d cf8
x p i b 1F7
x p i w rdi
t means temporary (one-shot, erased immediately after it hits), p means permanent (breakpoint stays after it hits)
e means executable, r means read+WRITE memory (not only read but WRITE also !!!), w means write memory (but not read), i means I/O port access
b=byte, w=word, d=dword, q=qword
for executable hw bp the size is always byte and you cannot set the size, for all other types you must set the size of breakpoint area
for size word, dword, qword the address must be aligned at the size boundary
executable hw bp hits before the instruction is executed (so RIP is pointing to the begin of instruction which caused the breakpoint), readwrite/write/io breakpoints generate debug exception after they complete (so RIP is pointing after the instruction which caused the breakpoint = to the begin of following instruction)
type this command for more
h x
even better, read AMD/Intel manual(s)
N.B. fdbg tries to enable I/O breakpoints also (the feature seems to be present on all new CPUs) so you may capture accesses to I/O ports also


[0B]
y command shows / erases hardware breakpoint
without any param it shows all hardware breakpoints (if there is any)
if you want to erase one of 4 hw bp, use commands like
y 0
y 1
y 2
y 3


[0C]
b command shows / sets software breakpoint(s) (writes int3 into memory), advantage is that the count of sw breakpoints not limited to 4 (there are only 4 hw bp), disadvantages are that it modifies memory so then some CRC may report wrong result, they can't capture I/O ports / memory accesses but are only executable, they are only temporary = one-shot (I don't have enough time to implement permanent software breakpoints, use hw bp where it is possible)
without any param it shows all software breakpoints (if there is any), which is command:
b
to set a breakpoint at an instruction at the address 2b280f0 use command:
b 2b280f0
to set a breakpoint at an instruction 20h bytes after the current RIP use command:
b rip+20
N.B. that the address must be at the instruction starting address (not somewhere in the middle of more-bytes instruction !!!)


[0D]
a command shows / erases software breakpoint(s)
without any param it shows all software breakpoints (if there is any), which is command:
a
to delete breakpoint at address 2b280f0, use command like
a 2b280f0
N.B. that software breakpoint is also erased immediatelly after it hits (because they are implemented only as temporary = one shot)

[0E]
c command prints disassembled code from current RIP or from the last address where previous command c finished, the start address is updated at every exception (debug, breakpoint, bug)
without any param, it starts to print instructions from current RIP, this is command
c
with address it starts to print instructions from the address, this is command like
c 2b69528
if you want to print more instructions after the last one printed, then continue with bare command c (without address), that is:
c
once you type the command c, you can just press ENTER to repeat it until you want
if you then want to disassemble from current RIP use command like:
c rip
also commands like these are possible:
c r15-8000
c rsi


[0F]
d command dumps data from address, this is something like:
d 1234560
to dump more, type bare command
d
end press ENTER until you like
also these commands are possible:
d rdi
d rsp+100
d r8+8


[10]
e dumps stack from current RSP (bare command e) or from address (command like e 10008510)
N.B. that the format is different than format in command d, here the data are displayed as qwords
sample of commands:
e
e rsp
e rsp-20
e rdi+ffff0000ffffba98


[11]
m command modifies memory, you must specify whether to write byte, word, dword, qword, ascii string, unicode string
samples:
m a [302521]='string'
m u [1000015EF]="text"
m a [rsi]="isn't"
m u [rax-3]="'"Hello!"'"
m b [281021]=Fd
m w [rsp-5C]=1234
m d [rip+512a]=AB91f50e
m q [r14-14]=2000000000000051
m q [rsp+28]=rsp+30
N.B. inline assembler is not implemented, you can change instruction only using hexa encodings
for printing memory use commands c, d, e


[12]
p writes a byte into IO port 80h, it is useful for some motheboards with small hexadecimal display connected to this debug port
example:
p 00
p A5
p FF


[13]
pressing ENTER repeats previous command (if there is any valid command), so if you want to do 20 single steps, then once type s and then 20-times hit ENTER


fdbg tries to enable all features at the CPU like I/O hardware breakpoints, debug branches MSRs, YMM registers (note that you can work with YMM registers but disasm procedure doesn't support them so you'll see some strange disassembled instructions, but you can single step / trace through them, CPU interprets them correctly, only disasm not, I don't have time to upgrade disasm engine, If you miss that much you must improve the engine by yourself, fdbg source code is available and will be forever)

N.B. that commands are case sensitive and there is allowed only 1 space for separating strings, no spaces at the end of command are allowed, any violation causes to abort the command and message about bad syntax is printed


WARNING !!!
Never try to put breakpoints inside of SIMPLE_TEXT_OUTPUT_INTERFACE.OutputString, EFI_BOOT_SERVICES.WaitForEvent, EFI_SYSTEM_TABLE.ReadKeyStroke.
This is because fdbg uses these UEFI services to print to screen and get input from keyboard. Interrupting this by breakpoint halts the PC (only reset/power button helps).


----------------------------------------------------------------


documentation of debugger design


[0] assumption(s):
- only one CPU is executing even in SMP systems (bootstrap CPU bit 8. of MSR 0000001Bh MSR_APIC_BASE.BSP=1, application CPUs are halted)
  this ensures hooked interrupts on the CPU will work (else the only way is to send IPI to all CPUs so every CPU installs hook)
- no one exception handler is used by UEFI (as int 08h was used in BIOS for IRQ0 system timer and int 09h for IRQ1 keyboard interrupt)
- debugger as well the debuggee share the same virtual memory (have the same virtual memory translation tables, the same base from CR3)
- executables are running at CPL0

[1] debugger does these tasks in this order:
loads_executable
puts_breakpoint at executable entrypoint
hook_exceptions (int 00h - int 1Fh)
starts the executable
unhook_exceptions (int 00h - int 1Fh)

[2] accessing memory (e.g. doing memory inspection, writing int3 breakpoint):
hook these exceptions: #UD Invalid opcode, #NP Segment not present, #SS Stack, #GP General protection, #PF Page fault, #AC Alignment check
try to access the memory
if no hook hit then the access was successfull, else the memory is not present, in case of partial transfer calculate the count of bytes transferred and return it
unhook exceptions

[3] exception handler:
save all registers
enable interrupts
wait for command requested by user
disable interrupts
restore all registers
IRETQ to the interrupted debuggee (the IRETQ also loads RFLAGS.IF of the debuggee, RFLAGS was saved when CPU encountered the exception at which SS, RSP, RFLAGS, CS, RIP were pushed into the stack)

