Developing Keil MDK-ARM Projects with VisualGDB

This tutorial shows how to use VisualGDB to build a project using the Keil MDK-ARM compiler, use VisualGDB IntelliSense including CodeMap and debug it with Visual Studio.

  1. Before you begin, ensure you can build your Keil project with the uVision IDE:01-keilprj
  2. Open Visual Studio and begin creating a new Embedded VisualGDB project:02-newprj
  3. Select “Import a project” and “Generate a Makefile” options:03-import
  4. On the next page choose the ARM toolchain and then select “specify flags manually” and leave the flags empty for now:04-flags
  5. Specify the directory containing your Keil project. If you do not have too many files, you can select “show all source files together” to flatten the Keil directory structure:05-keildir
  6. Select a debug method. In this example we will use OpenOCD with ST-Link integrated in the STM32F411RE Nucleo board:06-debugDo not forget to manually select the device type. As you are specifying the flags manually, VisualGDB won’t be able to detect it.
  7. By default VisualGDB will not import the assembly startup file (.s), so copy it to the new project directory and add it to the project via context menu:07-asmfile
  8. Now we need to import the compiler and linker flags from the uVision project. Open target properties in uVision and copy the compiler control string:08-cppflags
  9. The options specified there need to be split into 3 categories:
    • Include directories and preprocessor macros. Those options are important for IntelliSense.
    • Keil-specific options. Those options are not gcc-compatible and would produce IntelliSense errors.
    • Options like “-c” and “-o” that specify file names. They are automatically added by the Makefile.

    The table below classifies the options for a sample uVision project:

    Option Meaning Category
    -c Compile file Ignored
    -g Produce debug symbols Irrelevant for IntelliSense
    -O0 Disable optimization Irrelevant for IntelliSense
    –apcs=interwork Specify function call convention Keil-specific
    –split_sections Equivalent to -ffunction-sections Keil-specific
    -I <…> Specify include search path Relevant for IntelliSense
    -D<…> Define a preprocessor macro Relevant for IntelliSense
    -o “…” Output file Ignored
    –depend “…” Dependency file Ignored
  10. First of all, we will add IntelliSense-relevant options to the project properties. Open VisualGDB Project Properties on the first page and specify the defines and include directories extracted from uVision flags:09-includesNote that you can specify paths relative to the project directory (e.g. RTE means <project directory>\RTE).
  11. Go to the Makefile Settings page and remove all GCC-specific flags except -g and -O0 also present in the uVision flags (also disable the binary file generation):10-makeflagsWe will specify Keil-specific flags later. If you enter them here, it will confuse the Clang-based IntelliSense engine.
  12. Press OK. VisualGDB will recheck the flags and update IntelliSense. Open your source file and ensure that there are no IntelliSense errors and the syntax coloring is working correctly:11-isense
  13. Now we will change the Makefile to use the Keil compiler instead of GCC and will provide the Keil-specific flags. Open the mcu.mak file:12-makfile
  14. The mcu.mak file specifies the compiler tools (CC, CXX, LD and AR) and the common flags that are used by the IntelliSense engine. As we don’t want to break IntelliSense, instead of modifying this file, we will specify them in a different place. Before that, open the Keil Target options and take a note of the linker flags: 13-linkerflags
  15. Now we will specify the Keil-specific flags. Create a file called keil.mak in the project directory and put the remaining C/C++ flags and linker flags there as well as the overrides for CC,CXX and other tools:14-keilmakHere is a sample contents of keil.mak:
  16. Finally we need to adjust the Makefile. First, specify “ADDITIONAL_MAKE_FILES += keil.mak” between the two include directives:

    Then replace all occurences of “-MD -MF” with
    “–depend”. A typical C++ build rule will look like this:
  17. If you build your project now you will get several errors regarding missing HAL symbols:15-nohal
  18. To fix that, first locate the HAL folder via uVision: 16-halfolder
  19. The source files in that folder implement various HAL functions:folder
  20. Right-click in Solution Explorer, select Add->Existing Item and add the HAL source files to the project.Then build your solution. Double-check in the build log that VisualGDB used the Keil compiler and linker:17-keilcomp
  21. Now press F5 to begin debugging your program. Once you verify that the LED is blinking, set a breakpoint somewhere in your program loop and verify that you can evaluate the variables:18-debug
  22. Right-click on the main() function, open CodeMap and select “show called functions” to verify the Clang IntelliSense:19-codemap
  23. The ELF file produced by the Keil linker will have one fundamental problem. If you load it with GDB, the global variables will have a value of -1 instead of the normal initial values:20-initdata
  24.  This happens because of the differences between
    the GNU and the Keil linker. The GNU linker computes 2
    different addresses for the data section: the address where
    it will be loaded (VMA) and the address in the FLASH memory
    where its contents will be stored (LMA). GDB then puts the
    section contents at the LMA address and the startup code
    copies it to VMA. Keil linker sets the LMA = VMA, so instead
    of writing the data section to FLASH, GDB puts it directly
    into RAM where it gets overwritten by the Keil statup code
    that expects it to be in the FLASH. To fix this we first
    need to find out the section name. Run arm-eabi-objdump.exe -x <ELF FILE> on your ELF file (objdump tool is a part of the GCC toolchain):
  25. We will fix this in two steps: first we’ll run the Keil tool called fromelf.exe to convert the ELF file into a .bin file that will reflect the FLASH memory contents the way Keil tools expect it:

    Then we will edit the .elf file using the objcopy tool to replace the data that gets copied to the FLASH memory with the contents of the .bin file. This will ensure that GDB puts the initialization data into the FLASH in the same way as Keil expects it:
  26. The modified fragment of the Makefile will look this way (section names and addresses depend on the device and should be taken from the dump of the original elf file):

    The diagram below explains the dependencies between the .axf (KEIL ELF), .bin and the final .elf files:21-files
  27. Now build the project and verify that the variables are initialized properly:22-vars
  28. If they are still broken, double-check that the ROM section has been placed at the correct address and that its size matches the size of the bin file by inspecting the dump produced by the arm-eabi-objdump tool:
  29. As we have created the project manually, VisualGDB won’t display the peripheral registers for your device. To fix that, create a normal VisualGDB project for your device and search the <MCU FAMILY>.xml file in the project directory for the MCUDefinitionFile element, e.g.:
  30. Find the file (it will have a .gz extension) in %LOCALAPPDATA%\VisualGDB\EmbeddedBSPs and copy it to the directory containing the mcu.xml file of the imported Keil project. Then modify the mcu.xml file to reference the device definition file:
  31. Start debugging your project again and verify that the hardware registers are now shown:23-hwregs
  32. Finally we will fix the parsing of Keil error messages. As they use a different syntax than GCC, VisualGDB won’t recognize them by default. You can reproduce this by adding a simple function that will produce a warning and an error and trying to build your file:

  33. In order to support the “<file>, line <line>” format, download the BuildMessageTemplates.xml file and save it into the VisualGDB directory under Program Files. If you build your project now, Visual Studio will show the errors and warnings correctly:
  34. If you are actively using VisualGDB with the Keil compiler, let us know on our forums. Once we gather enough feedback, we will simplify the integration with the Keil tools in the next VisualGDB versions.