The Minnowboard Chronicles Episode 17: Using LBR Trace without Source Code

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).

Alan Sguigna