memtest86plus/doc/HOW_TO_DEBUG_WITH_GDB.md
01e3 71d061a4c7
Merge bootsect.S and setup.S into header.S (#467)
* Merge bootsect.S and setup.S into header.S

Currently, Memtest86+ provides two (three counting mbr.S) different
headers: bootsect.S and header.S that together with setup.S are used
to build the final binaries:
 - bootsect.S for booting from the legacy BIOS, used in memtest.bin.
 - header.S for booting from the UEFI, used in memtest.efi.

Both support loading by an intermediate bootloader. Also, both are
derived from the Linux kernel, but from a very different chapter in its
evolution. FDD loading was removed a very long time ago, many, many
years before adding PE/COFF headers for UEFI. Also, the PE/COFF headers
are large (even larger for x86-64) and take a lot of very limited space,
leaving almost no headroom for the actual code.

This commit merges bootsect.S and setup.S into header.S adding BIOS
(FDD) loading support, which should eventually enable to maintain a single,
all-in-one binary that can be booted either by an intermediate bootloader
(such as GRUB or LILO), UEFI and BIOS. Easier and less confusing for users,
as I have continued to see cases when people assume memtest.bin is needed
for non-UEFI systems, even if never intended for FDD loading.

To ensure we have enough space for both the boot code (limited to 512B
including Linux boot header and address of the New EXE header) and
UEFI headers, the PE/COFF headers got moved into the ".setup" section
of the code where *currently* we have 1024B total and about 90B free.
Should more space be needed in the future, we can grow that section or
perhaps even better - move PE/COFF into its own section that will not
even need to be loaded.

Overall, the code got copied from bootsect.S and setup.S with very, very
minimal changes, most importantly:

 - Dedicated routine for printing strings (print_string) got added and
   lives in the ".bootsect" section.

 - Print "Loading " message almost immediately after BIOS loads and
   calls code from the .bootsect" section, even before setting up
   the custom FDD parameter table. The sooner the better.

 - Set DX to 0x0000, as the code depends on its value. While the BIOS is
   expected to set DL register to the boot drive number which is 0 for FDD,
   to my knowledge there is no guarantee for DH to have any particular
   value. Also, when executing "MZ" signature as x86 instructions,
   the original value of DX becomes instantly and irrevocably lost. This
   is fine as we only support booting from FDD anyway.

 - Print "Memtest86+" and its version after loading the ".setup" section.
   We can get the string from mt86plus_version (kernel_version), no need
   to duplicate in the boot sector where we are very space limited and as
   a bonus we can provide the version information.

As diffstat shows a lot of code changed and added, it may be easier use
the following commands:
 diff -Nur bootsect.S header.S
 diff -Nur setup.S header.S

While there are several more changes and fixes I would like to make in the
boot code (some of them were already included in the earlier RFC version
[1] before we got UEFI signing support added) for now I tried this to be as
least disruptive as possible and avoid unnecessary code changes to make
the review easier.

[1] https://github.com/memtest86plus/memtest86plus/pull/136

* Bye bye memtest.bin and memtest.efi, long live mt86plus

With the efi binary providing the BIOS floppy loading functionality,
there is no need to support separate memtest.bin file any longer.

Remove memtest.bin from {build32,build64}/Makefile, replace memtest.bin
amd memtest.efi with mt86plus eveywhere to avoid confusion with
other memtests.

Also, unify build32/Makefile and build64/Makefile by removing
unnecessary differences between them, like extra spaces or
different OBJS order.

Finally, update README.md, HOW_TO_DEBUG_WITH_GDB.md and other
places that had a reference to memtest.{bin,efi}. Note that for
debug_memtest.sh, mt86plus.efi is used.
2025-01-27 19:43:24 +01:00

5.6 KiB

How to debug mt86plus in QEMU with GDB

debug_memtest.sh is a script that allows memtest86plus developers to set up a debugging environment with GDB in QEMU.
It calls the make debug target of a modified Makefile to create an additional debug-symbol file called memtest.debug.
The symbols of this file are loaded with an offset (in accordance with the loading location of the efi-image) into GDB to match the exact addresses of the symbols in memory.

Prerequisites

  • this approach was tested on Ubuntu 18.04 - 22.04
  • the debug script is created only for the efi 64-bit version
  • qemu-system-x86_64 and ovmf must be installed

How to run

  • navigate to build64 directory
  • run ./debug_memtest.sh
  • or type ./debug_memtest.sh -h for help

Remarks - create own gdbscript

It is possible to provide an own gdb-script. Name it 'gdbscript' and place it in the build64 directory. This script will automatically be loaded when gdb starts. !! But be careful when cleaning the directory by './debug_memtest.sh -c'. It also removes 'gdbscript'. !! Make sure that you have made a copy of 'gdbscript' when running this command.

Navigate inside Qemu/UEFI

  • wait until UEFI-Shell is loaded
  • type "fs0:" - Enter
  • type "mt86plus.efi" - Enter

Inside GDB

When GDB is running, it stops at the first breakpoint at main(). Feel free to add further breakpoints or continue with c.

Remarks - auto-boot memtest86+

In step Navigate inside QEMU/UEFI, you have to navigate to the directory which contains mt86plus.efi and manually launch it.

If you want to automatically boot from mt86plus.efi, there is an additional step required to add memtest to the first place at the bootorder:

When the UEFI Shell is running, type bcfg boot add 0 FS0:\EFI\boot\BOOT_X64.efi "memtest" and confirm with Enter. The directory "\EFI\boot" and the file "BOOT_X64.efi" are automatically created by the debug-script.

You can add memtest command line parameters as "optional data" to the boot entry created above, e.g. for usbdebug: bcfg boot -opt 0x0 ^"usbdebug^" Note the UEFI-shell escaped quotes. Alternatively you can create a UTF-16 file with the parameters, e.g. with edit params.txt and add its contents: bcfg boot -opt 0x0 FS0:\params.txt

When you run the script the next time, mt86plus.efi should run without previous user interaction.

!! But be careful when cleaning the directory by './debug_memtest.sh -c'. It also removes this setting. !! Make sure that you have made a copy of 'OVMF*'-files when running this command.

Clean directory

'debug_memtest.sh' has an own clean procedure which cleans additional files not mentioned in Makefile's 'make clean' target. When you run this command, make sure that you have saved 'gdbscript' and/or OVMF* files if there are custom changes.

To clean the directory, type ./debug_memtest.sh -c

Possible features/alternatives and further considerations

Detection of Image Base

To assign the correct address for all debugging symbols, it is neccessary to add an offset to the values in memtest.debug (the file containing the debug symbols). This offset consists of the IMAGE_BASE and the BASE_OF_CODE.
Both values are defined in memtest86plus/boot/header.S

  • IMMUTABILITY OF ALL CONDITIONS

if you assume, that these values will never change during the development phase of memtest86plus AND mt86plus.efi is always loaded at this preferred address in qemu-system-x86_64 (which seems to be the case) then it is possible to hardcode the offset in the script (for the implementation see debug_memtest_simple.sh)

  • ADAPTABILITY TO DEVELOPMENT CHANGES

if there is a chance, that these values WILL change during the development phase but mt86plus.efi is always loaded at this preferred address then the value can be read from header.S by the debug script just right before starting the debugging (for an example, see debug_memtest_full.sh)

  • EXPECTED ERRATIC BEHAVIOUR OF QEMU

If it is expected that mt86plus.efi is NOT always loaded at the same address, it is inevitable to determine the actual loading address first. This approach comprises a DEBUG-build of OVMF.fd (which requires the cloning of the whole edk2-repository and manually build OVMF.fd). With this DEBUG-version of OVMF.fd it is possible to write the loading addresses of all modules into a debug.log. This proceeding has been tested successfully but is actually not implemented in one of the srcipts.

Handle relocation of memtest

memtest86plus relocates itself during the test procedures. As the script loads the symbol table with a given offset, debugging is only possible when the code is located at the original position. There are several ways to deal with relocation:

  • IGNORE RELOCATION

Just ignore the fact that at a part of the time the symbols are not recognized by gdb as gdb has no information about the symbols, when memtest86plus has been relocated. It is still possible to debug the code since memtest86plus jumps sooner or later back to the original position and all precedures which are executed at one location are also executed at the other position. BUT: If a bug is position-dependent (i.e. it occurs only at the relocated position), you are not able to debug it.

  • DISABLE RELOCATION

TODO: Is it possible to deactivate relocation? E.g. by outcommenting some code or setting a flag? Does it have benefits over the first approach?

  • FOLLOW RELOCATION

If the position after relocation is expected to be always the same, then you can just load the symbol table twice. This is done in debug_memtest_simple.sh (the offsets are 0x201000 and 0x400000). If the locations can vary then the offsets must be determined dynamically ... todo: how?