My curiosity got the better of me this week. I decided to play detective, and see what I can learn from LBR trace data if I pretend I don’t have access to the source code.
In Episode 16, I learned about how Last Branch Record (LBR) trace can be used to look at the true flow of program execution, which may or may not be easily gleaned from just looking at the static view within the SourcePoint Code window. Having access to the source code where the system “breaks” makes it very easy to understand what is going on. But there are situations where the source code and symbols are unavailable: it is obfuscated within some confidential parts of UEFI, comes from a third-party driver, etc. Or alternatively, there are situations where the symbols are available but not the source code. Becoming a top-notch debugger sometimes means we need to roll up our sleeves and make do with what we have.
To simulate this kind of debugging experience, I decided to run a simulation with SourcePoint. I would break at a random point within DXE, with LBR Trace active. Then I would backtrace code execution by looking at the LBR MSR source and destination addresses, and see what I might be able to glean.
There were a couple of preparation steps I needed to take beforehand. Firstly, I wanted to write a short SourcePoint macro that dumped all the LBR addresses, so I wouldn’t have to do that by hand tediously. This macro simply looks like this:
define ord8 i=40
define ord8 msrvalue_from_address = 0
define ord8 msrvalue_to_address = 0
for (I = 40; I <= 47; i++) {
msrvalue_from_address = msr(i)
msrvalue_to_address = msr(i + 20)
printf(“%x %x %x \n”, i, msrvalue_from_address, msrvalue_to_address)
i += 1
}
Secondly, although the DXE modules are relocatable, I’ve found that from boot to boot, the entry point addresses of the DXE modules do not change. In fact, when I run the DXE macro within SourcePoint, I always get the same output, an excerpt of which is below.
DxeCore Entry: 0000000078852300L Base: 0000000078852000L "c:\myworksspace2\Build\Vlv2TbltDevicePkg\DEBUG_VS2012x86\X64\MdeModulePkg\Core\Dxe\DxeMain\DEBUG\DxeCore.efi"
PcdDxe Entry: 0000000077BA62FCL Base: 0000000077BA6000L "c:\myworksspace2\Build\Vlv2TbltDevicePkg\DEBUG_VS2012x86\X64\MdeModulePkg\Universal\PCD\Dxe\Pcd\DEBUG\PcdDxe.efi"
ReportStatusCodeRouterRuntimeDxe Entry: 00000000780A62FCL Base: 00000000780A6000L "c:\myworksspace2\Build\Vlv2TbltDevicePkg\DEBUG_VS2012x86\X64\MdeModulePkg\Universal\ReportStatusCodeRouter\RuntimeDxe\ReportStatusCodeRouterRuntimeDxe\DEBUG\ReportStatusCodeRouterRuntimeDxe.efi"
StatusCodeHandlerRuntimeDxe Entry: 000000007809E2FCL Base: 000000007809E000L "c:\myworksspace2\Build\Vlv2TbltDevicePkg\DEBUG_VS2012x86\X64\MdeModulePkg\Universal\StatusCodeHandler\RuntimeDxe\StatusCodeHandlerRuntimeDxe\DEBUG\StatusCodeHandlerRuntimeDxe.efi"
So, I want to put the addresses of the DXE module entry points in a table, so I can easily map the addresses I get out of LBR trace to a piece of software. An excerpt of this is below:
Module |
Entry |
Base |
PchReset |
7807B03C |
7807A000 |
SmmControl |
7808603C |
78085000 |
RuntimeDxe |
780902FC |
78090000 |
CpuIoDxe |
780982FC |
78098000 |
StatusCodeHandlerRuntimeDxe |
7809E2FC |
78090000 |
ReportStatusCodeRouterRuntimeDxe |
780A62FC |
780A6000 |
DxeCore |
78852300 |
78852000 |
So, here we go. I boot the Minnowboard and then halt it while in the DXE phase, as it waits to get to the shell. Here’s the output of my macro:
40 78098e3f 780986d0
41 780986d5 780986e3
42 780986f7 78098704
43 7809872f 7809873d
44 78098743 78098a05
45 78098a10 78098a1c
46 78098a5a 78098a93
47 78098aa7 78099180
Also, typing in msr(1C9) gives x’7’, and given that the lowest significant 3 bits of the TOS Pointer MSR (MSR_LASTBRANCH_TOS, address 1C9H) contains a pointer to the MSR in the LBR stack that contains the most recent branch, interrupt, or exception recorded, the last “from_address” is x’78098aa7’ and the last “to_address” is x’78099180’.
Pretty cool, huh? Even without source code, based upon the addresses, you can see that the software is likely somewhere within CpuIoDxe.
And, if you want to cheat a little, taking the last branch “to_address”, x’78098190’, is an offset X’e84’ (or decimal 3,716) from the entry point of CpuIoDxe, address x’780982fc’. You can look at the CpuIoDxe.map file in the source build to estimate what function within CpuIoDxe we might be in. As it turns out, it’s somewhere within MmioWrite64():
Address Public by Value
0001:00000bb8 InternalMathDivRemU64x32
0001:00000c10 MmioRead8
0001:00000c38 MmioWrite8
0001:00000c64 MmioRead16
0001:00000cc8 MmioWrite16
0001:00000d30 MmioRead32
0001:00000d90 MmioWrite32
0001:00000df4 MmioRead64
0001:00000e58 MmioWrite64
0001:00000ec0 IoRead8
For a great video on what the SourcePoint GUI looks like, take a peek at our webpage here: SourcePoint for Intel.
A good eBook on trace features is at Intel Trace Hub (note: it’s free, but requires registration).