Pulling apart Dell UEFI images and messing with ACPI
This is a quick post to write down the method that worked for me to acquire and pull apart a Dell UEFI image to get the executables out, and how to poke at ACPI.
The specific issue I was curious about and trying to find more information
about is the rtsx
card reader NVMe regression on the Dell
XPS 15 9560, which is the laptop I use.
The regression was caused by the card reader driver no longer setting a
register that forces the PCIe CLKREQ#
pin to be always pulled low, which was,
in effect, making the kernel no longer ask the chipset for PCIe clock all the
time.
However, this is even more confusing, because this is essentially a force-disabling bit for attempting to do PCIe ASPM, which was never enabled in the first place on the machine!
For some reason, the card reader ceasing to ask the chipset for clock all the time (which, given that ASPM is purportedly disabled in the ACPI tables, should be a no-op!) caused the NVMe SSD, which has nothing to do with the card reader, to lose its PCIe link after the card reader driver loads. Further, this happened to every kind of SSD, so it must be some kind of platform bug.
If you have any ideas of why this is happening, send me an email at <bug at jade dot fyi>, I am very curious.
Extracting the firwmare
I acquired a copy of the firmware from fwupd here. This is a Microsoft cabinet file. I then used BIOSUtilities and p7zip to extract it:
mkdir bios
mv ../4d77eabfe13e3d153dfe9f19b570de40cc90260ef7229b2ca070e06b5c840040-Dell_XPS_15_9560_Precision_5520_System_BIOS_Ver.1.24.0.cab bios
(cd bios; 7z x *.cab)
python Dell_PFS_Extract.py -i bios -o bios_ex
This then produces some files:
BIOSUtilities » ls bios_ex/firmware.bin_extracted/Firmware/1\ firmware\ --\ *
'bios_ex/firmware.bin_extracted/Firmware/1 firmware -- 1 System BIOS with BIOS Guard v1.24.0.bin'
'bios_ex/firmware.bin_extracted/Firmware/1 firmware -- 2 Embedded Controller v1.0.29.bin'
'bios_ex/firmware.bin_extracted/Firmware/1 firmware -- 3 Intel Management Engine (VPro) Update v11.8.86.3877.bin'
'bios_ex/firmware.bin_extracted/Firmware/1 firmware -- 4 Main System TI Port Controller 0 v7.0.0.31.bin'
'bios_ex/firmware.bin_extracted/Firmware/1 firmware -- 5 System Board Map v1.0.1.bin'
'bios_ex/firmware.bin_extracted/Firmware/1 firmware -- 6 PCR0 XML v0.0.0.1.xml'
'bios_ex/firmware.bin_extracted/Firmware/1 firmware -- 7 Model Information v1.0.0.0.txt'
From here, you can use UEFITool on these files:
UEFITool firmware.bin_extracted/Firmware/1\ firmware\ --\ 1\ System\ BIOS\ with\ BIOS\ Guard\ v1.24.0.bin
From here you can go hunting through the data in there or try UEFIExtract
.
You can find a PE32 image called Setup somewhere in the UEFI image, and extract
it. With such an image, you can use ifrextractor
to pull out
the BIOS options. Acquire a copy like so:
ifrextractor.nix
:
{ rustPlatform, fetchFromGitHub }:
rustPlatform.buildRustPackage {
pname = "ifrextractor";
version = "0.0.1";
src = fetchFromGitHub {
owner = "longsoft";
repo = "ifrextractor-rs";
rev = "f40b9be0da561ede62f3988072100550a73d5386";
sha256 = "sha256-No0H91iMcOQlE0Hcc+w02w5CP3M+Ixct+92+XsreIik=";
};
cargoSha256 = "sha256-smVHEBhjUcy4ApyJzhV31sMTB87Cdq59fxkSJsS1/cw=";
}
nix-build -E 'with import <nixpkgs> {}; callPackage ./ifrextractor.nix'
Then extract the setup stuff with the following:
ifrextractor firmware.bin_extracted/Section_PE32_image_Setup_body.efi
Then read the extremely large file named like:
Section_PE32_image_Setup_body.efi.0.0.en-US.ifr.txt
.
If you did want to tamper with hidden settings, you could potentially have such bad ideas with similar methods to the following (please take a backup of the NVRAM first to not brick your computer!):
- https://ristovski.github.io/posts/inside-insydeh2o/#peeking-inside-the-bios-image
- https://hansdegoede.livejournal.com/25413.html
If you want to stick the thing in Ghidra, there does exist an extension, efiSeek, but I couldn't really tell if it was providing any useful analysis results.
ACPI
In investigating the power management issue I was curious about, I wanted to understand what was in the ACPI tables and what was going on. These tables are probably somewhere in the UEFI image as well, but I am not sure where.
You can dump the ACPI tables from a running system using acpidump
from acpica-tools
:
mkdir acpi && cd acpi
sudo acpidump -b
iasl -d *.dat
If you want to get values of ACPI variables, you can use the ACPI debugger from the Linux kernel distribution:
First, enable the right Kconfig options:
{ lib, config, ... }: {
boot.kernelPackages = pkgs.linuxPackages.extend (self: super: {
kernel = super.kernel.override (old: {
kernelPatches = old.kernelPatches ++ [
{
name = "acpi_nonsense";
patch = null;
extraStructuredConfig = with lib.kernel; {
ACPI_DEBUGGER = yes;
ACPI_DEBUGGER_USER = module;
DEVMEM = yes;
STRICT_DEVMEM = no;
IO_STRICT_DEVMEM = option no;
};
}
];
});
});
}
Then:
cd some-linux-sources
make acpi
sudo ./power/acpi/acpidbg
Inside the debugger you can print out things:
- Evaluate UCSI
Evaluating \UCSI
Evaluation of \UCSI returned object 00000000a57a6a95, external buffer length 18
[Integer] = 0000000000000000
You can also use Dump
to get information about a variable, such as its
offset, if you need to peek/poke the memory.
If you want to tamper with the ACPI tables for investigating a potential fix, there are a couple of ways to do it.
At runtime, you can load an overlay:
modprobe acpi_configfs
cd /sys/kernel/config/acpi/table/
mkdir my-ssdt ; cat ~youruser/somewhere/my-ssdt.aml > my-ssdt/aml
# unload with:
rmdir my-ssdt
It's also possible to replace the ACPI tables with the initrd:
Create a cpio archive with kernel/firmware/acpi/*.dat
, then prepend it to the
actual initrd.
Conclusion
This whole affair did not achieve much, but it was very interesting, and I have more of an idea how firmware works on x86.
For further information:
There may also exist schematics of the machine online, but I cannot confirm this.