0

I'm using the arm-none-eabi-gcc toolchain onlinux and am having trouble creating archives that link to other archives. As a specific example, the base archive (libstm32f4_hal.a) should have a function HAL_SPI_GetState and using nm, it seems that it does

$ nm libstm32f4_hal.a
stm32f4xx_hal_spi.o:
...
0000158d T HAL_SPI_GetState
00001495 T HAL_SPI_IRQHandler
00000271 T HAL_SPI_Init
...

I then want to make another archive (libstm32f4_bsp.a) which makes use of libstm32f4_hal.a. The second archive seems to build correctly, but when I try and link against these libraries, the linker throws the following error.

cube/Drivers/BSP/STM32F4-Discovery/libstm32f4_bsp.a(stm32f4_discovery.o): In function `SPIx_Init':
cube/Drivers/BSP/STM32F4-Discovery/stm32f4_discovery.c:313: undefined reference to `HAL_SPI_GetState'

When I check libstm32f4_bsp.a using nm, indeed the function HAL_SPI_GetState is not defined.

$ nm libstm32f4_bsp.a
stm32f4_discovery.o:
...
         U HAL_SPI_GetState
         U HAL_SPI_Init
...

Here is the makefile I'm using to build the second archive.

CC=arm-none-eabi-gcc
AR=arm-none-eabi-ar

HAL_DIR = ../../STM32F4xx_HAL_Driver

###########################################

vpath %.c

CFLAGS  = -g -O2 -Wall
CFLAGS += -mlittle-endian -mthumb -mcpu=cortex-m4 -mthumb-interwork
CFLAGS += -mfloat-abi=hard -mfpu=fpv4-sp-d16
CFLAGS += -ffreestanding -nostdlib

CFLAGS += -I$(HAL_DIR)/Inc
CFLAGS += -I../../CMSIS/Device/ST/STM32F4xx/Include
CFLAGS += -I../../CMSIS/Include

SRCS = stm32f4_discovery.c stm32f4_discovery_accelerometer.c stm32f4_discovery_audio.c


OBJS = $(SRCS:.c=.o)

.PHONY: all clean

all: libstm32f4_bsp.a

%.o : %.c
    $(CC) $(CFLAGS) -c -o $@ $^ -L$(HAL_DIR) -lstm32f4_hal

libstm32f4_bsp.a: $(OBJS)
    $(AR) -rvs $@ $(OBJS)

clean:
    rm -f $(OBJS) libstm32f4_bsp.a

Could someone explain why this might be happening? I'd be happy to provide more details but am relatively inexperienced with what I'm trying to do here and am not completely sure what else would be helpful.

The context is, I'm trying to build the demonstration code for the STM32F4-Discovery board. It basically sets up the board to act as a USB mouse. Since the USB stack is huge, my plan is to build the hardware abstraction layer (HAL) as an archive, then build the board-specific middleware as an archive linked to the HAL archive, and then in my final build link the demonstration code to these two archives.

Thanks for the help.

EDIT:

Here is the makefile I'm using to build the project.

# STM32 Makefile for GNU toolchain and openocd
#
# This Makefile fetches the Cube firmware package from ST's' website.
# This includes: CMSIS, STM32 HAL, BSPs, USB drivers and examples.
#
# Usage:
#   make cube       Download and unzip Cube firmware
#   make program        Flash the board with OpenOCD
#   make openocd        Start OpenOCD
#   make debug      Start GDB and attach to OpenOCD
#   make dirs       Create subdirs like obj, dep, ..
#   make template       Prepare a simple example project in this dir
#
# Copyright 2015 Steffen Vogel
# License   http://www.gnu.org/licenses/gpl.txt GNU Public License
# Author    Steffen Vogel <post@steffenvogel.de>
# Link      http://www.steffenvogel.de
#
# edited for the STM32F4-Discovery

# A name common to all output files (elf, map, hex, bin, lst)
TARGET     = demo

# Take a look into $(CUBE_DIR)/Drivers/BSP for available BSPs
BOARD      = STM32F4-Discovery
BSP_BASE   = stm32f4_discovery

OCDFLAGS   = -f board/stm32f4discovery.cfg
GDBFLAGS   =

#EXAMPLE   = Templates
EXAMPLE    = Demonstrations

# MCU family and type in various capitalizations o_O
MCU_FAMILY = stm32f4xx
MCU_LC     = stm32f401xc
MCU_MC     = STM32F407xx
MCU_UC     = STM32F407VG




# Your C files from the /src directory
SRCS       = main.c
SRCS      += system_$(MCU_FAMILY).c
SRCS      += stm32f4xx_it.c

# Basic HAL libraries
#SRCS      += stm32f4xx_hal_rcc.c stm32f4xx_hal_rcc_ex.c stm32f4xx_hal.c stm32f4xx_hal_cortex.c stm32f4xx_hal_gpio.c $(BSP_BASE).c

# USB .c
SRCS      += usbd_conf_template.c usbd_core.c usbd_ctlreq.c usbd_ioreq.c
SRCS      += usbd_hid.c
SRCS      += usbd_desc.c
SRCS      += stm32f4xx_hal_pcd.c

SRCS      += stm32f4_discovery_accelerometer.c stm32f4xx_hal_tim.c

# Directories
OCD_DIR    = /usr/share/openocd/scripts

CUBE_DIR   = cube

BSP_DIR    = $(CUBE_DIR)/Drivers/BSP/$(BOARD)
HAL_DIR    = $(CUBE_DIR)/Drivers/STM32F4xx_HAL_Driver
CMSIS_DIR  = $(CUBE_DIR)/Drivers/CMSIS

DEV_DIR    = $(CMSIS_DIR)/Device/ST/STM32F4xx

CUBE_URL   = http://www.st.com/st-web-ui/static/active/en/st_prod_software_internet/resource/technical/software/firmware/stm32cubef4.zip

# that's it, no need to change anything below this line!

###############################################################################
# Toolchain

PREFIX     = arm-none-eabi
CC         = $(PREFIX)-gcc
AR         = $(PREFIX)-ar
OBJCOPY    = $(PREFIX)-objcopy
OBJDUMP    = $(PREFIX)-objdump
SIZE       = $(PREFIX)-size
GDB        = $(PREFIX)-gdb

OCD        = openocd

###############################################################################
# Options

# Defines
DEFS       = -D$(MCU_MC) -DUSE_HAL_DRIVER

# Debug specific definitions for semihosting
DEFS       += -DUSE_DBPRINTF

# Include search paths (-I)
INCS       = -Itemplate/inc
INCS      += -I$(BSP_DIR)
INCS      += -I$(CMSIS_DIR)/Include
INCS      += -I$(DEV_DIR)/Include
INCS      += -I$(HAL_DIR)/Inc

# USB .h
INCS      += -I$(CUBE_DIR)/Middlewares/ST/STM32_USB_Device_Library/Core/Inc
INCS      += -I$(CUBE_DIR)/Middlewares/ST/STM32_USB_Device_Library/Class/HID/Inc

# Library search paths
LIBS       = -L$(CMSIS_DIR)/Lib

# Compiler flags
CFLAGS     = -Wall -g -std=c99 -Os
CFLAGS    += -mlittle-endian -mcpu=cortex-m4 -march=armv7e-m -mthumb
CFLAGS    += -mfpu=fpv4-sp-d16 -mfloat-abi=hard
CFLAGS    += -ffunction-sections -fdata-sections
CFLAGS    += $(INCS) $(DEFS)

CFLAGS    += -mthumb-interwork

# Linker flags
LDFLAGS    = -Wl,--gc-sections -Wl,-Map=$(TARGET).map $(LIBS) -Ttemplate/$(MCU_LC).ld

# Enable Semihosting
LDFLAGS   += --specs=rdimon.specs -lc -lrdimon

# Source search paths
VPATH      = ./template/src
VPATH     += $(BSP_DIR)
VPATH     += $(HAL_DIR)/Src
VPATH     += $(DEV_DIR)/Source/

VPATH     += $(CUBE_DIR)/Middlewares/ST/STM32_USB_Device_Library/Core/Src
VPATH     += $(CUBE_DIR)/Middlewares/ST/STM32_USB_Device_Library/Class/HID/Src

OBJS       = $(addprefix template/obj/,$(SRCS:.c=.o))
DEPS       = $(addprefix template/dep/,$(SRCS:.c=.d))

# Prettify output
V = 0
    Q = @
    P = > /dev/null
endif

###################################################

.PHONY: all dirs program debug template clean

all: $(TARGET).elf

-include $(DEPS)

dirs: template/dep template/obj cube
template/dep template/obj template/src template/inc:
    @echo "[MKDIR]   $@"
    $Qmkdir -p $@

template/obj/%.o : %.c | dirs
    @echo "[CC]      $(notdir $<)"
    $Q$(CC) $(CFLAGS) -c -o $@ $< -MMD -MF template/dep/$(*F).d -L$(HAL_DIR) -lstm32f4_hal -L$(BSP_DIR) -lstm32f4_bsp

$(TARGET).elf: $(OBJS)
    @echo "[LD]      $(TARGET).elf"
    $Q$(CC) $(CFLAGS) $(LDFLAGS) template/src/startup_$(MCU_LC).s $^ -o $@ -L$(HAL_DIR) -lstm32f4_hal -L$(BSP_DIR) -lstm32f4_bsp
    @echo "[OBJDUMP] $(TARGET).lst"
    $Q$(OBJDUMP) -St $(TARGET).elf >$(TARGET).lst
    @echo "[SIZE]    $(TARGET).elf"
    $(SIZE) $(TARGET).elf

openocd:
    $(OCD) -s $(OCD_DIR) $(OCDFLAGS)

program: all
    $(OCD) -s $(OCD_DIR) $(OCDFLAGS) -c "program $(TARGET).elf verify reset"

debug:
    @if ! nc -z localhost 3333; then \
        echo "\n\t[Error] OpenOCD is not running! Start it with: 'make openocd'\n"; exit 1; \
    else \
        $(GDB)  -ex "target extended localhost:3333" \
            -ex "monitor arm semihosting enable" \
            -ex "monitor reset halt" \
            -ex "load" \
            -ex "monitor reset init" \
            $(GDBFLAGS) $(TARGET).elf; \
    fi

cube:
    rm -fr $(CUBE_DIR)
    wget -O /tmp/cube.zip $(CUBE_URL)
    unzip /tmp/cube.zip
    mv STM32Cube* $(CUBE_DIR)
    chmod -R u+w $(CUBE_DIR)
    rm -f /tmp/cube.zip

template: cube template/src template/inc
    cp -ri $(CUBE_DIR)/Projects/$(BOARD)/$(EXAMPLE)/Src/* template/src
    cp -ri $(CUBE_DIR)/Projects/$(BOARD)/$(EXAMPLE)/Inc/* template/inc
    cp -i $(DEV_DIR)/Source/Templates/gcc/startup_$(MCU_LC).s template/src
    cp -i $(CUBE_DIR)/Projects/$(BOARD)/$(EXAMPLE)/TrueSTUDIO/STM32F4-DISCO/$(MCU_UC)_FLASH.ld template/$(MCU_LC).ld

clean:
    @echo "[RM]      $(TARGET).elf"; rm -f $(TARGET).elf
    @echo "[RM]      $(TARGET).map"; rm -f $(TARGET).map
    @echo "[RM]      $(TARGET).lst"; rm -f $(TARGET).lst
    @echo "[RMDIR]   template/dep"          ; rm -fr template/dep
    @echo "[RMDIR]   template/obj"          ; rm -fr template/obj

rm:
    rm -rf template
ifeq ($V, 0)

Here is the failed linking command.

`arm-none-eabi-gcc -Wall -g -std=c99 -Os -mlittle-endian -mcpu=cortex-m4 -march=armv7e-m -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -Itemplate/inc -Icube/Drivers/BSP/STM32F4-Discovery -Icube/Drivers/CMSIS/Include -Icube/Drivers/CMSIS/Device/ST/STM32F4xx/Include -Icube/Drivers/STM32F4xx_HAL_Driver/Inc -Icube/Middlewares/ST/STM32_USB_Device_Library/Core/Inc -Icube/Middlewares/ST/STM32_USB_Device_Library/Class/HID/Inc -DSTM32F407xx -DUSE_HAL_DRIVER -DUSE_DBPRINTF -mthumb-interwork -Wl,--gc-sections -Wl,-Map=demo.map -Lcube/Drivers/CMSIS/Lib -Ttemplate/stm32f401xc.ld --specs=rdimon.specs -lc -lrdimon template/src/startup_stm32f401xc.s template/obj/main.o template/obj/system_stm32f4xx.o template/obj/stm32f4xx_it.o template/obj/usbd_conf_template.o template/obj/usbd_core.o template/obj/usbd_ctlreq.o template/obj/usbd_ioreq.o template/obj/usbd_hid.o template/obj/usbd_desc.o template/obj/stm32f4xx_hal_pcd.o template/obj/stm32f4_discovery_accelerometer.o template/obj/stm32f4xx_hal_tim.o -o demo.elf -Lcube/Drivers/STM32F4xx_HAL_Driver -lstm32f4_hal -Lcube/Drivers/BSP/STM32F4-Discovery -lstm32f4_bsp'

I loaded the project to github here in case anyone is motivated enough to try and build the project themselves. As a side note, I am running Arch linux.

user2027202827
  • 1,234
  • 11
  • 29
  • 1
    If that's the makefile for the library which builds successfully, what's the makefile/build command for the thing which _doesn't_ work? – Notlikethat Jun 18 '16 at 22:32
  • Both `libstm32f4_hal.a` and `libstm32f4_bsp.a` build successfully. The problem is `libstm32f4_bsp.a` uses functions from `libstm32f4_hal.a`, but those functions are undefined in `libstm32f4_bsp.a` (even though the build runs without error). – user2027202827 Jun 18 '16 at 22:35
  • 1
    Well, yeah, you've built two separate libraries, one of which has a dependency on the other. Hence the interest in whether the _third_ thing is actually linking against both of them, and [in the right order](http://stackoverflow.com/a/24675715/1356926) to boot. – Notlikethat Jun 18 '16 at 22:44
  • Ok, I would have thought the function should have been definited in both libraries but maybe that is part of my lack of understanding. I attached the project makefile and the first error it produces is the one about `undefined reference to `HAL_SPI_GetState'`. Thanks for the help. – user2027202827 Jun 18 '16 at 22:48
  • in the file for building the second archive: this line: `.PHONY: libstm32f4_bsp.a` is not correct. Remember that a '.PHONY:` directive is to tell the `make` utility that the target names following the `.PHONY:` (on the same line) do not produce an actual file of that same name. Well the target: `libstm32f4_bsp.a` does actually produce a file of that name. However, the targets: `all` and `clean` do not produce a file of that same name. So the line should be: `.PHONY: all clean` – user3629249 Jun 19 '16 at 22:45
  • 1
    a `makefile`, in general, executes the items in the same order as they are listed. (however dependency lists will revise that order.) regarding this line: `-include $(DEPS)` generally, that should not be visible when the target selected from the command line is `clean` otherwise all the dependency files are regenerated when trying to cleanup. So that line should be at the end of the file and written with this wrapper: `ifneq "$(MAKECMDGOALS)" "clean" -include $(DEP) endif` – user3629249 Jun 19 '16 at 22:50
  • in the posted makefiles, I do not see any definition for the macro `$Q` – user3629249 Jun 19 '16 at 23:04
  • Thanks for your feedback @user3629249. Regarding your first comment, that is an excellent point. I've updated the makefile accordingly. I'm not familiar with `ifneq "$(MAKECMDGOALS)" "clean" -include $(DEP) endif` so I will certainly look into that. – user2027202827 Jun 24 '16 at 00:45
  • As for your third comment, what you mention is actually supposed to be two seperate lines. I believe `dirs` is supposed to be a shorthand to check if any of the input files have changed (if I'm interpreting that correctly). I'm working off the template seen here https://github.com/stv0g/stm32cube-gcc/blob/master/Makefile. Maybe I should add another line break to make it more clear. – user2027202827 Jun 24 '16 at 00:45
  • In response to your last comment about `$Q`, I believe that is defined in the section for 'pretty output' `V = 0 Q = @ P = > /dev/null endif` – user2027202827 Jun 24 '16 at 00:47
  • While all your comments are certainly appreciated, I've still been unable to address the problem I am having with the program not linking correctly. I did some tests since I am not very familiar with creating archives, and it seems that having function undefined in an archive that links to another archive is perfectly acceptable. What I mean by that is, running `nm` on `libstm32f4_bsp.a` should not neccesarily show `T HAL_SPI_GetState`. I'm not really sure where to look next for the problem. – user2027202827 Jun 24 '16 at 00:51
  • hobenkr, what was the exact failed linking command? Can you [add `-(` and `-)`](http://stackoverflow.com/a/5651895/196561) around your 2 .a libs, like `gcc ... -( -lstm32f4_bsp -lstm32f4_hal -)` and rerun failed command? – osgx Jun 24 '16 at 01:10
  • Hello @osgx. I added the failed command to the end of my post since it is disgustingly long. With regards to `-(` and `-)`, do you mean for me to enclose the linking search directory as well such as `-( -L$(HAL_DIR) -lstm32f4_hal -L$(BSP_DIR) -lstm32f4_bsp -)`? Doing this seems to produce a syntax error: `syntax error near unexpected token `('`. – user2027202827 Jun 24 '16 at 01:19
  • hobenkr, try `--start-group` your_archives `--end-group` – osgx Jun 24 '16 at 02:22
  • Yes, I tried that after your first suggestion didn't work with the same results unfortunately. – user2027202827 Jun 24 '16 at 03:16
  • most of the macros in the actual `makefile` are using the `=`. Using that results in the macro being re-evaluated each time it is reference. I do not see any events where the result of the evaluation would change. Therefore, strongly suggest using `:=` so the macro(s) will only be evaluated once. – user3629249 Jun 24 '16 at 17:53
  • the gitlib `makefile` the rule: `template/obj/%.o : %.c | dirs` has this reference to libraries in the compile recipe line: `-L$(HAL_DIR) -lstm32f4_hal -L$(BSP_DIR) -lstm32f4_bsp` However, no libraries are needed at compile time. – user3629249 Jun 24 '16 at 18:43
  • the gitlib `makefile` there is the target `rm`, but the list in the `.PHONY` statement is missing that target – user3629249 Jun 24 '16 at 18:44
  • one of the main reasons for making a macro using a executable name is to provide the actual path to the executable so `make` (and the associated shell) do not use some other executable file, findable by the `$PATH` environment variable. I.E. the macro definitions for GDB, etc should include the path to the executables (probably as part of the `PREFIX` macro) – user3629249 Jun 24 '16 at 18:49
  • strongly suggest separating the many options in the macro `CFLAGS` two two groups. one group that only effects compiling and one group that only effects linking. Then `CFLAGS` to only contain options that effect compiling and `LFLAGS` to only contain options that effect linking (some flags, may be in both macros. Then when only linking, in the `makefile` recipes, do not use the `$(CFLAGS)` macro. – user3629249 Jun 24 '16 at 19:02
  • this line: `$(CC) $(CFLAGS) $(LDFLAGS) template/src/startup_$(MCU_LC).s $^ -o $@ -L$(HAL_DIR) -lstm32f4_hal -L$(BSP_DIR) -lstm32f4_bsp` should be written as: `$(CC) $(CFLAGS) $(LDFLAGS) template/src/startup_$(MCU_LC).s $^ -o $@ -L$(HAL_DIR) -L$(BSP_DIR) --start-group -lstm32f4_hal -lstm32f4_bsp --end-group` – user3629249 Jun 24 '16 at 19:12
  • 2
    if neither the `(- ... -)` nor the `--start-group ... --end-group` work then use: `-L$(HAL_DIR) -L$(BSP_DIR) -lstm32f4_hal -lstm32f4_bsp -lstm32f4_hal -lstm32f4_bsp` – user3629249 Jun 24 '16 at 19:35
  • @user3629249, while your suggestion of passing both archives twice seemed a bit silly at first, it did actually help me verify that the suggestion osgx made of reversing the order of my archives was correct. As for your suggestions to update my makefile, I do appreciate them and will eventually get to that. My understanding of makefiles is not as good as it should be though, so I'm trying to make a minimal number of structural changes to the template I started with until I can at least get the program to compile since it's demonstration code provided ST Microelectronics. – user2027202827 Jun 25 '16 at 23:21

1 Answers1

2

Your failed command was something like

arm-none-eabi-gcc ...-mcpu=cortex-m4 .. template/obj/main.o \
-lstm32f4_hal ... -lstm32f4_bsp

And your libstm32f4_hal.a archive has function HAL_SPI_GetState defined, and this function is used by libstm32f4_bsp.a archive.

So, you have wrong order of archives in your linking command. You should know that linker (ld, which is usually called by gcc to do the actual linking step) works on input archives (.a files) from left to right, check man page of ld(1) at http://linux.die.net/man/1/ld

Normally, an archive is searched only once in the order that it is specified on the command line.

You may try to change order of hal and bsp archives (-lstm32f4_bsp ... -lstm32f4_hal), but if it fails too, then you have circular dependency. You may mention libraries several times in the linking command (-lstm32f4_bsp ... -lstm32f4_hal -lstm32f4_bsp), or just instruct linker to iterate over sublist of archives with -( and -) or --start-group and --end-group linker options, as described in man and in https://stackoverflow.com/a/5651895/196561. The options should be placed around your libraries with circular dependencies: --start-group -lstm32f4_bsp ... -lstm32f4_hal --end-group

Full description of options from the man page of ld

-( archives -) --start-group archives --end-group

The archives should be a list of archive files. They may be either explicit file names, or -l options.

The specified archives are searched repeatedly until no new undefined references are created. Normally, an archive is searched only once in the order that it is specified on the command line. If a symbol in that archive is needed to resolve an undefined symbol referred to by an object in an archive that appears later on the command line, the linker would not be able to resolve that reference. By grouping the archives, they all be searched repeatedly until all possible references are resolved.

Using this option has a significant performance cost. It is best to use it only when there are unavoidable circular references between two or more archives.

When you use gcc to call linker, use -Wl, prefix to pass options to linker, if your gcc don't recognize them: -Wl,--start-group -lstm32f4_bsp ... -lstm32f4_hal -Wl,--end-group

When you use shell/make, you may need to quote ( and ) in short variant of options, for example with single quotes: '-Wl,-(' -lstm32f4_bsp ... -lstm32f4_hal '-Wl,-)'

Community
  • 1
  • 1
osgx
  • 90,338
  • 53
  • 357
  • 513
  • You may be right about the linking order. I tried all the modifications you suggested but still am getting errors. I need to go to bed right now but will hopefully take another look at it tomorrow. Thank you for trying to help. I posted the project to github in case you want to try building it yourself: https://github.com/hobenkr88/ArmUsb – user2027202827 Jun 24 '16 at 03:20
  • Ok, I did manage to pass `-Wl,--start-group -lstm32f4_bsp....... -Wl,--end-group`. I also did as suggested in another comment and passed the archives in the form `A B A B` which verified you were correct to have me reverse the order I was passing my archives in. Unfortunately I'm still having a linking problem but it's taken the question in a different direction than what I've asked so I'll probably end up starting a new question to adress this since appending to this question would be confusion for anyone having similar trouble. Thanks for your help. – user2027202827 Jun 25 '16 at 23:39