Sparkfun RED-V 红板祼机调试记录

 

本文是对RED-V红板裸机程序编译过程的分析

本文是对RED-V红板裸机程序编译过程的分析

Sparkfun RED-V 红板祼机调试记录

分析一下Freedom Studio的构建过程,分析的过程是:

  1. Makefile
  2. 链接脚本
  3. 镜像下载
  4. 调试方法

Makefile文件的解析

Freedome Studio工程的makefile指令如下:

make all CONFIGURATION=debug 

那么我们来分析一下工程的Makefile文件,这个文件位于wsFreedomStudio/red-v工程目录下

确定编译配置

PROGRAM = sifive-welcome
TARGET = sifive-hifive1-revb

# The configuration defaults to Debug. Valid choices are:
#   - debug
#   - release
CONFIGURATION ?= debug

BSP_DIR ?= $(abspath bsp)
SRC_DIR ?= $(abspath src)

载入目标板的配置

# Include the BSP settings
include $(BSP_DIR)/settings.mk

ifeq ($(RISCV_LIBC),)
	RISCV_LIBC=nano
endif

ifeq ($(RISCV_LIBC),nano)
	# 裸机需要使用libgloss底层库
	LIBMETAL_EXTRA=-lmetal-gloss
	METAL_WITH_EXTRA=--with-builtin-libgloss
	SPEC=nano
endif

LINK_TARGET = default
RISCV_XLEN := 32
MTIME_RATE_HZ_DEF=32768

settings.mk的内容如下:

RISCV_ARCH = rv32imac
RISCV_ABI = ilp32
RISCV_CMODEL = medlow
RISCV_SERIES = sifive-3-series

TARGET_TAGS = board jlink
TARGET_DHRY_ITERS = 20000000
TARGET_CORE_ITERS = 5000
TARGET_FREERTOS_WAIT_MS = 1000
TARGET_INTR_WAIT_CYCLE  = 0

确定工具链相关内容

工具链

CROSS_COMPILE ?= riscv64-unknown-elf

RISCV_GCC     := $(CROSS_COMPILE)-gcc
SEGGER_JLINK_EXE := JLinkExe
SEGGER_JLINK_GDB_SERVER := JLinkGDBServer

编译选项

RISCV_CFLAGS    += -march=$(RISCV_ARCH) -mabi=$(RISCV_ABI) -mcmodel=$(RISCV_CMODEL)

最终的编译选项为:

-march=rv32imac -mabi=ilp32 -mcmodel=medlow -ffunction-sections -fdata-sections -I/Users/wilson/wsFreedomStudio/red-v/bsp/install/include --specs=nano.specs -DMTIME_RATE_HZ_DEF=32768 -fcommon -O0 -g

链接选项

# Turn on garbage collection for unused sections
RISCV_LDFLAGS += -Wl,--gc-sections
# Turn on linker map file generation
RISCV_LDFLAGS += -Wl,-Map,$(PROGRAM).map
# Turn off the C standard library
RISCV_LDFLAGS += -nostartfiles -nostdlib
# Find the archive files and linker scripts
RISCV_LDFLAGS += -L$(sort $(dir $(abspath $(filter %.a,$^)))) -T$(abspath $(filter %.lds,$^))

# Link to the relevant libraries
RISCV_LDLIBS += -Wl,--start-group -lc -lgcc -lm -lmetal $(LIBMETAL_EXTRA) -Wl,--end-group

最终的链接选项为:

-Wl,--gc-sections -Wl,-Map,sifive-welcome.map -nostartfiles -nostdlib -L/Users/wilson/wsFreedomStudio/red-v/bsp/install/lib/debug/ -T/Users/wilson/wsFreedomStudio/red-v/bsp/metal.default.lds  sifive-welcome.c  -Wl,--start-group -lc -lgcc -lm -lmetal -lmetal-gloss -Wl,--end-group

加载配置Makefile

# Load the configuration Makefile
CONFIGURATION_FILE = $(wildcard $(CONFIGURATION).mk)
include $(CONFIGURATION).mk

就是加载./debug.mk配置文件

# Set the optimization level
RISCV_ASFLAGS += -O0
RISCV_CFLAGS += -O0
RISCV_CXXFLAGS += -O0

# Enable debug
RISCV_ASFLAGS += -g
RISCV_CFLAGS += -g
RISCV_CXXFLAGS += -g

输出目标

PROGRAM_ELF ?= $(SRC_DIR)/$(CONFIGURATION)/$(PROGRAM).elf
PROGRAM_HEX ?= $(SRC_DIR)/$(CONFIGURATION)/$(PROGRAM).hex
PROGRAM_LST ?= $(SRC_DIR)/$(CONFIGURATION)/$(PROGRAM).lst

Make目标

.PHONY: all
all: software

.PHONY: software
software: $(PROGRAM_ELF)
software: $(PROGRAM_HEX)

# 代码文件,所有src/目录下的c/h/S文件
PROGRAM_SRCS = $(wildcard $(SRC_DIR)/*.c) $(wildcard $(SRC_DIR)/*.h) $(wildcard $(SRC_DIR)/*.S)

# elf: srcs, *.a, default.lds
$(PROGRAM_ELF): \
		$(PROGRAM_SRCS) \
		$(BSP_DIR)/install/lib/$(CONFIGURATION)/libmetal.a \
		$(BSP_DIR)/install/lib/$(CONFIGURATION)/libmetal-gloss.a \
		$(BSP_DIR)/metal.$(LINK_TARGET).lds
	mkdir -p $(dir $@)
	$(MAKE) -C $(SRC_DIR) $(basename $(notdir $@)) \
		PORT_DIR=$(PORT_DIR) \
		PROGRAM=$(PROGRAM) \
		AR=$(RISCV_AR) \
		CC=$(RISCV_GCC) \
		CXX=$(RISCV_GXX) \
		ASFLAGS="$(RISCV_ASFLAGS)" \
		CCASFLAGS="$(RISCV_CCASFLAGS)" \
		CFLAGS="$(RISCV_CFLAGS)" \
		CXXFLAGS="$(RISCV_CXXFLAGS)" \
		XCFLAGS="$(RISCV_XCFLAGS)" \
		LDFLAGS="$(RISCV_LDFLAGS)" \
		LDLIBS="$(RISCV_LDLIBS)" \
		FREERTOS_METAL_VENV_PATH="$(FREERTOS_METAL_VENV_PATH)"
	mv $(SRC_DIR)/$(basename $(notdir $@)) $@
	mv $(SRC_DIR)/$(basename $(notdir $@)).map $(dir $@)
	touch -c $@
	$(RISCV_OBJDUMP) --source --all-headers --demangle --line-numbers --wide $@ > $(PROGRAM_LST)
	$(RISCV_SIZE) $@

这段内容的输出为:

mkdir -p /Users/wilson/wsFreedomStudio/red-v/src/debug/
make -C /Users/wilson/wsFreedomStudio/red-v/src sifive-welcome \
	PORT_DIR= \
	PROGRAM=sifive-welcome \
	AR=/Applications/FreedomStudio.app/Contents/Eclipse/SiFive/riscv64-unknown-elf-toolchain-10.2.0-2020.12.8/bin/riscv64-unknown-elf-ar \
	CC=/Applications/FreedomStudio.app/Contents/Eclipse/SiFive/riscv64-unknown-elf-toolchain-10.2.0-2020.12.8/bin/riscv64-unknown-elf-gcc \
	CXX=/Applications/FreedomStudio.app/Contents/Eclipse/SiFive/riscv64-unknown-elf-toolchain-10.2.0-2020.12.8/bin/riscv64-unknown-elf-g++ \
	ASFLAGS="-march=rv32imac -mabi=ilp32 -mcmodel=medlow --specs=nano.specs -O0 -g" \
	CCASFLAGS="-march=rv32imac -mabi=ilp32 -mcmodel=medlow -I/Users/wilson/wsFreedomStudio/red-v/bsp/install/include --specs=nano.specs" \
	CFLAGS="-march=rv32imac -mabi=ilp32 -mcmodel=medlow -ffunction-sections -fdata-sections -I/Users/wilson/wsFreedomStudio/red-v/bsp/install/include --specs=nano.specs -DMTIME_RATE_HZ_DEF=32768 -fcommon -O0 -g" \
	CXXFLAGS="-march=rv32imac -mabi=ilp32 -mcmodel=medlow -ffunction-sections -fdata-sections -I/Users/wilson/wsFreedomStudio/red-v/bsp/install/include --specs=nano.specs -DMTIME_RATE_HZ_DEF=32768 -O0 -g" \
	XCFLAGS="-DMETAL_WAIT_CYCLE=0" \
	LDFLAGS="-Wl,--gc-sections -Wl,-Map,sifive-welcome.map -nostartfiles -nostdlib -L/Users/wilson/wsFreedomStudio/red-v/bsp/install/lib/debug/ -T/Users/wilson/wsFreedomStudio/red-v/bsp/metal.default.lds" \
	LDLIBS="-Wl,--start-group -lc -lgcc -lm -lmetal -lmetal-gloss -Wl,--end-group" \
	FREERTOS_METAL_VENV_PATH="/Users/wilson/wsFreedomStudio/red-v/venv"


make[1]: Entering directory '/Users/wilson/wsFreedomStudio/red-v/src'

/Applications/FreedomStudio.app/Contents/Eclipse/SiFive/riscv64-unknown-elf-toolchain-10.2.0-2020.12.8/bin/riscv64-unknown-elf-gcc -march=rv32imac -mabi=ilp32 -mcmodel=medlow -ffunction-sections -fdata-sections -I/Users/wilson/wsFreedomStudio/red-v/bsp/install/include --specs=nano.specs -DMTIME_RATE_HZ_DEF=32768 -fcommon -O0 -g  -Wl,--gc-sections -Wl,-Map,sifive-welcome.map -nostartfiles -nostdlib -L/Users/wilson/wsFreedomStudio/red-v/bsp/install/lib/debug/ -T/Users/wilson/wsFreedomStudio/red-v/bsp/metal.default.lds  sifive-welcome.c  -Wl,--start-group -lc -lgcc -lm -lmetal -lmetal-gloss -Wl,--end-group -o sifive-welcome

make[1]: Leaving directory '/Users/wilson/wsFreedomStudio/red-v/src'

mv /Users/wilson/wsFreedomStudio/red-v/src/sifive-welcome /Users/wilson/wsFreedomStudio/red-v/src/debug/sifive-welcome.elf

mv /Users/wilson/wsFreedomStudio/red-v/src/sifive-welcome.map /Users/wilson/wsFreedomStudio/red-v/src/debug/

touch -c /Users/wilson/wsFreedomStudio/red-v/src/debug/sifive-welcome.elf

/Applications/FreedomStudio.app/Contents/Eclipse/SiFive/riscv64-unknown-elf-toolchain-10.2.0-2020.12.8/bin/riscv64-unknown-elf-objdump --source --all-headers --demangle --line-numbers --wide /Users/wilson/wsFreedomStudio/red-v/src/debug/sifive-welcome.elf > /Users/wilson/wsFreedomStudio/red-v/src/debug/sifive-welcome.lst

/Applications/FreedomStudio.app/Contents/Eclipse/SiFive/riscv64-unknown-elf-toolchain-10.2.0-2020.12.8/bin/riscv64-unknown-elf-size /Users/wilson/wsFreedomStudio/red-v/src/debug/sifive-welcome.elf
   text	   data	    bss	    dec	    hex	filename
  30914	   2796	   3256	  36966	   9066	/Users/wilson/wsFreedomStudio/red-v/src/debug/sifive-welcome.elf

/Applications/FreedomStudio.app/Contents/Eclipse/SiFive/riscv64-unknown-elf-toolchain-10.2.0-2020.12.8/bin/riscv64-unknown-elf-objcopy -O ihex /Users/wilson/wsFreedomStudio/red-v/src/debug/sifive-welcome.elf /Users/wilson/wsFreedomStudio/red-v/src/debug/sifive-welcome.hex

清除目标

.PHONY: clean-software
clean-software:
	$(MAKE) -C $(SRC_DIR) PORT_DIR=$(PORT_DIR) clean
	rm -rf $(SRC_DIR)/$(CONFIGURATION)
.PHONY: clean
clean: clean-software

总结

结过分析,我们通过

make all CONFIGURATION=debug 

生成了四个文件

wsFreedomStudio/red-v/src/debug/sifive-welcome.elf
wsFreedomStudio/red-v/src/debug/sifive-welcome.hex
wsFreedomStudio/red-v/src/debug/sifive-welcome.lst
wsFreedomStudio/red-v/src/debug/sifive-welcome.map

下载版本文件

目标:搞清下面make的过程

make PROGRAM=sparkfun-welcome TARGET=sparkfun-redv CONFIGURATION=release upload

makefile文件内容:

#############################################################
# Configuration
#############################################################

# Allows users to create Makefile.local or ../Makefile.project with
# configuration variables, so they don't have to be set on the command-line
# every time.
extra_configs := $(wildcard Makefile.local ../Makefile.project)
ifneq ($(extra_configs),)
$(info Obtaining additional make variables from $(extra_configs))
include $(extra_configs)
endif

TARGET_ROOT  ?= $(abspath .)
PROGRAM_ROOT ?= $(abspath .)

# Allow BOARD as a synonym for TARGET
# 允许BOARD作为TARGET的同义词
ifneq ($(BOARD),)
TARGET ?= $(BOARD) # TARGET=sparkfun-redv
endif

# Default PROGRAM and TARGET
PROGRAM ?= hello # PROGRAM=sparkfun-welcome
TARGET ?= $(shell find $(TARGET_ROOT)/bsp/* -type d | head -n 1 | rev | cut -d '/' -f 1 | rev)

# The configuration defaults to Debug. Valid choices are:
#  - debug
#  - release
CONFIGURATION ?= debug

# Setup differences between host platforms
ifeq ($(OS),Windows_NT)
    SED_RE_FLAG = -r
else
    UNAME_S := $(shell uname -s)
    ifeq ($(UNAME_S),Linux)
        SED_RE_FLAG = -r
    endif
    ifeq ($(UNAME_S),Darwin)
        SED_RE_FLAG = -E
    endif
endif

# Default to use relase configuration For Benchmark programs, like Coremark and Dhrystone.
# 默认使用relase配置 对于基准测试程序,如Coremark和Dhrystone。
ifeq ($(PROGRAM),dhrystone)
CONFIGURATION = release
endif

# Coremark require PORT_DIR set for different OS, freedom-metal for us!
# Coremark要求为不同的操作系统设置 PORT_DIR,对我们来说是freedom-metal
ifeq ($(PROGRAM),coremark)
CONFIGURATION = release
ifeq ($(PORT_DIR),)
PORT_DIR = freedom-metal
endif
endif

# SRC_DIR = ./software/sparkfun-welcome
SRC_DIR = $(PROGRAM_ROOT)/software/$(PROGRAM)

# PROGRAM_ELF = $(SRC_DIR)/release/sparkfun-welcome.elf
PROGRAM_ELF = $(SRC_DIR)/$(CONFIGURATION)/$(PROGRAM).elf
PROGRAM_HEX = $(SRC_DIR)/$(CONFIGURATION)/$(PROGRAM).hex
PROGRAM_LST = $(SRC_DIR)/$(CONFIGURATION)/$(PROGRAM).lst

#############################################################
# BSP Loading
#############################################################

# Finds the directory in which this BSP is located, ensuring that there is
# exactly one.
# 找到bsp目录:
BSP_DIR := $(wildcard $(TARGET_ROOT)/bsp/$(TARGET))

ifeq ($(words $(BSP_DIR)),0)
$(error Unable to find BSP for $(TARGET), expected to find "bsp/$(TARGET)")
endif
ifneq ($(words $(BSP_DIR)),1)
$(error Found multiple BSPs for $(TARGET): "$(BSP_DIR)")
endif

#############################################################
# Standalone Script Include 独立的脚本包括
#############################################################

# The standalone script is included here because it needs $(SRC_DIR) and
# $(BSP_DIR) to be set.
#
# The standalone Makefile handles the following tasks:
#  - Including $(BSP_DIR)/settings.mk and validating RISCV_ARCH, RISCV_ABI
#  - Setting the toolchain path with CROSS_COMPILE and RISCV_PATH
#  - Providing the software and $(PROGRAM_ELF) Make targets for Metal
# 独立的 Makefile 处理以下任务。
# - 包括 $(BSP_DIR)/settings.mk 并验证 RISCV_ARCH, RISCV_ABI
# - 用CROSS_COMPILE和RISCV_PATH设置工具链路径
# - 为Metal提供软件和$(PROGRAM_ELF) 制作目标

include scripts/standalone.mk

#############################################################
# Prints help message
#############################################################
.PHONY: help
help:
	@echo " SiFive Freedom E Software Development Kit "
	@echo " Makefile targets:"
	@echo ""
	@echo " software [PROGRAM=$(PROGRAM)] [TARGET=$(TARGET)]"
	@echo "          [CONFIGURATION=$(CONFIGURATION)]:"
	@echo "    Builds the requested PROGRAM for the TARGET using the"
	@echo "    specified build CONFIGURATION."
	@echo ""
	@echo " metal [TARGET=$(TARGET)] [CONFIGURATION=$(CONFIGURATION)]"
	@echo "    Builds the Freedom Metal library for TARGET."
	@echo ""
	@echo " clean [PROGRAM=$(PROGRAM)] [TARGET=$(TARGET)]"
	@echo "       [CONFIGURATION=$(CONFIGURATION)]:"
	@echo "    Cleans compiled objects for a specified "
	@echo "    software program."
	@echo ""
	@echo " upload [PROGRAM=$(PROGRAM)] [TARGET=$(TARGET)]"
	@echo "        [CONFIGURATION=$(CONFIGURATION)]:"
	@echo "    For board and FPGA TARGETs, uploads the program to the"
	@echo "    on-board flash."
	@echo ""
	@echo " debug [PROGRAM=$(PROGRAM)] [TARGET=$(TARGET)]"
	@echo "       [CONFIGURATION=$(CONFIGURATION)]:"
	@echo "    For board and FPGA TARGETs, attaches GDB to the"
	@echo "    running program."
	@echo ""
	@echo " simulate [PROGRAM=$(PROGRAM)] [TARGET=$(TARGET)]"
	@echo "          [CONFIGURATION=$(CONFIGURATION)]:"
	@echo "    Simulates the program in the QEMU emulator."
	@echo ""
	@echo " standalone STANDALONE_DEST=/path/to/desired/location"
	@echo "            [PROGRAM=$(PROGRAM)] [TARGET=$(TARGET)]:"
	@echo "    Exports a program for a single target into a standalone"
	@echo "    project directory at STANDALONE_DEST."
	@echo ""
	@echo " open-docs"
	@echo "    Opens the Freedom E SDK documentation in your HTML"
	@echo "    viewer of choice. The documentation can also be found"
	@echo "    online at"
	@echo "      https://sifive.github.io/freedom-e-sdk-docs/index.html"

.PHONY: open-docs
open-docs: scripts/open-docs
	$^

.PHONY: clean
clean:

#############################################################
# Enumerate BSPs and Programs
#
# List all available boards and programs in a form that
# Freedom Studio knows how to parse.  Do not change the
# format or fixed text of the output without consulting the
# Freedom Studio dev team.
#############################################################
# 枚举BSP和程序
#
# 列出所有可用的板卡和程序的形式,以便于
# Freedom Studio知道如何解析。 请不要改变
# 格式或固定的输出文本,除非咨询
# Freedom Studio开发团队。
#############################################################

# Find all settings.mk with TARGET_REQUIRE_TAGS in TARGET_TAGS
# 在TARGET_TAGS中找到所有带有TARGET_REQUIRE_TAGS的设置.mk
MATCHING_SETTINGS = $(shell scripts/filter-targets $(TARGET_ROOT)/bsp $(TARGET_REQUIRE_TAGS))

# Get the name of the containing directory of all matching settings.mk
# 获取所有匹配设置的包含目录的名称.mk
MATCHING_TARGETS = $(patsubst $(TARGET_ROOT)/bsp/%/,%,$(dir $(MATCHING_SETTINGS)))

.PHONY: list-targets
list-targets:
	@echo bsp-list: $(sort $(MATCHING_TARGETS))

# Lists all available TARGET_TAGS
#
#  1. Find all settings.mk
#  2. Extract the TARGET_TAGS line
#  3. Extract the value of TARGET_TAGS
#  4. Split each tag onto a newline
#  5. Sort the lines
#  6. Find unique tags
#
.PHONY: list-target-tags
list-target-tags:
	@echo target-tags: $(shell find $(TARGET_ROOT)/bsp -name settings.mk | \
		xargs grep -he "TARGET_TAGS" | \
		sed $(SED_RE_FLAG) 's/TARGET_TAGS.*=(.*)/\1/' | \
		tr ' ' '\n' | \
		sort | \
		uniq)

# Metal programs are any submodules in the software folder
# 金属程序是软件文件夹中的任何子模块
.PHONY: list-programs
list-programs:
	@echo program-list: $(shell ls $(PROGRAM_ROOT)/software)

.PHONY: list-options
list-options: list-programs list-targets

#############################################################
# Import rules to build Freedom Metal
# 在这里进行编译
#############################################################

include scripts/libmetal.mk # 编译出hex、bin等文件

#############################################################
# Standalone Project Export
#############################################################

ifeq ($(STANDALONE_DEST),)
standalone:
	$(error Please provide STANDALONE_DEST to create a standalone project)
else # STANDALONE_DEST != ""

$(STANDALONE_DEST):
$(STANDALONE_DEST)/%:
	mkdir -p $@

ifneq ($(filter rtl,$(TARGET_TAGS)),)
# TARGETs with the "rtl" TARGET_TAG need elf2hex in their standalone project
standalone: \
		$(STANDALONE_DEST) \
		$(STANDALONE_DEST)/bsp \
		$(STANDALONE_DEST)/src \
		$(SRC_DIR) \
		freedom-metal \
		debug.mk \
		release.mk \
		scripts/elf2hex \
		scripts/standalone.mk \
		scripts/libmetal.mk
	cp -r $(addprefix $(BSP_DIR)/,$(filter-out build,$(shell ls $(BSP_DIR)))) $</bsp/

	cp -r freedom-metal $</

	find $</freedom-metal -name ".git*" | xargs rm -rf

	mkdir -p $</scripts
	cp -r scripts/elf2hex $</scripts

	find $</scripts/elf2hex -name ".git*" | xargs rm -rf

ifeq ($(PORT_DIR),)
	$(MAKE) -C $(SRC_DIR) clean
else
	$(MAKE) -C $(SRC_DIR) PORT_DIR=${PORT_DIR} clean
endif
	cp -r $(SRC_DIR)/* $</src/

	cp debug.mk $</debug.mk
	cp release.mk $</release.mk

	echo "PROGRAM = $(PROGRAM)" > $</Makefile
	echo "TARGET = ${TARGET}" >> $</Makefile
ifneq ($(PORT_DIR),)
	echo "PORT_DIR = $(PORT_DIR)" >> $</Makefile
endif
	echo "" >> $</Makefile
	echo "# The configuration defaults to Debug. Valid choices are:" >> $</Makefile
	echo "#   - debug" >> $</Makefile
	echo "#   - release" >> $</Makefile
	echo "CONFIGURATION ?= ${CONFIGURATION}" >> $</Makefile
	echo "" >> $</Makefile
	cat scripts/standalone.mk >> $</Makefile
	cat scripts/libmetal.mk >> $</Makefile
else # "rtl" not in TARGET_TAGS
standalone: \
		$(STANDALONE_DEST) \
		$(STANDALONE_DEST)/bsp \
		$(STANDALONE_DEST)/src \
		$(SRC_DIR) \
		freedom-metal \
		debug.mk \
		release.mk \
		scripts/standalone.mk \
		scripts/libmetal.mk
	cp -r $(addprefix $(BSP_DIR)/,$(filter-out build,$(shell ls $(BSP_DIR)))) $</bsp/

	cp -r freedom-metal $</

	find $</freedom-metal -name ".git*" | xargs rm -rf

ifeq ($(PORT_DIR),)
	$(MAKE) -C $(SRC_DIR) clean
else
	$(MAKE) -C $(SRC_DIR) PORT_DIR=${PORT_DIR} clean
endif
	cp -r $(SRC_DIR)/* $</src/

	cp debug.mk $</debug.mk
	cp release.mk $</release.mk

	echo "PROGRAM = $(PROGRAM)" > $</Makefile
	echo "TARGET = ${TARGET}" >> $</Makefile
ifneq ($(PORT_DIR),)
	echo "PORT_DIR = $(PORT_DIR)" >> $</Makefile
endif
	echo "" >> $</Makefile
	echo "# The configuration defaults to Debug. Valid choices are:" >> $</Makefile
	echo "#   - debug" >> $</Makefile
	echo "#   - release" >> $</Makefile
	echo "CONFIGURATION ?= ${CONFIGURATION}" >> $</Makefile
	echo "" >> $</Makefile
	cat scripts/standalone.mk >> $</Makefile
	cat scripts/libmetal.mk >> $</Makefile
endif # rtl in TARGET_TAGS

endif # STANDALONE_DEST

#############################################################
# Upload and Debug
#############################################################

ifneq ($(RISCV_OPENOCD_PATH),)
RISCV_OPENOCD=$(RISCV_OPENOCD_PATH)/bin/openocd
else
#if RISCV_OPENOCD_PATH is not set, just look on the PATH
RISCV_OPENOCD=openocd
endif

ifneq ($(filter jlink,$(TARGET_TAGS)),)
upload: $(PROGRAM_HEX)
	scripts/upload --hex $(PROGRAM_HEX) --jlink $(SEGGER_JLINK_EXE)
else
upload: $(PROGRAM_ELF)
	scripts/upload --elf $(PROGRAM_ELF) --openocd $(RISCV_OPENOCD) --gdb $(RISCV_GDB) --openocd-config bsp/$(TARGET)/openocd.cfg
endif

ifneq ($(filter jlink,$(TARGET_TAGS)),)
debug: $(PROGRAM_ELF)
	scripts/debug --elf $(PROGRAM_ELF) --jlink $(SEGGER_JLINK_GDB_SERVER) --gdb $(RISCV_GDB)
else
debug: $(PROGRAM_ELF)
	scripts/debug --elf $(PROGRAM_ELF) --openocd $(RISCV_OPENOCD) --gdb $(RISCV_GDB) --openocd-config bsp/$(TARGET)/openocd.cfg
endif

ifeq ($(findstring qemu,$(TARGET)),qemu)
ifeq ($(findstring rv32,$(RISCV_ARCH)),rv32)
simulate: $(PROGRAM_ELF)
	scripts/simulate --elf $(PROGRAM_ELF) --qemu $(QEMU_RISCV32) --qemu-config bsp/$(TARGET)/qemu.cfg
else # findstring rv32
simulate: $(PROGRAM_ELF)
	scripts/simulate --elf $(PROGRAM_ELF) --qemu $(QEMU_RISCV64) --qemu-config bsp/$(TARGET)/qemu.cfg
endif
else # findstring qemu
simulate:
	@echo "QEMU can't simulate target $(TARGET)!"
endif

freedom-e-sdk/scripts/libmetal.mk

#############################################################
# Compiles an instance of Metal targeted at $(TARGET)
# 编译一个针对$(TARGET)的Metal实例
#############################################################
METAL_SOURCE_PATH ?= freedom-metal
# 链接脚本:freedom-e-sdk/bsp/sparkfun-redv/metal.default.lds
METAL_LDSCRIPT	   = $(BSP_DIR)/metal.$(LINK_TARGET).lds
METAL_HEADER	   = $(BSP_DIR)/metal.h
METAL_INLINE       = $(BSP_DIR)/metal-inline.h
PLATFORM_HEADER	   = $(BSP_DIR)/metal-platform.h

METAL_PREFIX       = $(abspath $(BSP_DIR)/install)
METAL_BUILD_DIR    = $(abspath $(BSP_DIR)/build/$(CONFIGURATION))
METAL_LIB_DIR	   = $(abspath $(BSP_DIR)/install/lib/$(CONFIGURATION))

.PHONY: metal
metal: $(METAL_LIB_DIR)/stamp

$(METAL_BUILD_DIR)/Makefile:
	@rm -rf $(dir $@)
	@mkdir -p $(dir $@)
	cd $(dir $@) && \
		CFLAGS="$(RISCV_CFLAGS)" \
		$(abspath $(METAL_SOURCE_PATH)/configure) \
		--host=$(CROSS_COMPILE) \
		--prefix=$(METAL_PREFIX) \
		--libdir=$(METAL_LIB_DIR) \
		--disable-maintainer-mode \
		--with-preconfigured \
		--with-machine-name=$(TARGET) \
		--with-machine-header=$(abspath $(METAL_HEADER)) \
    --with-machine-inline=$(abspath $(METAL_INLINE)) \
		--with-platform-header=$(abspath $(PLATFORM_HEADER)) \
		--with-machine-ldscript=$(abspath $(METAL_LDSCRIPT)) \
		--with-builtin-libgloss
	touch -c $@

$(METAL_LIB_DIR)/stamp: $(BSP_DIR)/build/$(CONFIGURATION)/Makefile
	$(MAKE) -C $(abspath $(BSP_DIR)/build/$(CONFIGURATION)) install
	date > $@

$(METAL_LIB_DIR)/libriscv%.a: $(METAL_LIB_DIR)/stamp ;@:

$(METAL_LIB_DIR)/libmetal.a: $(METAL_LIB_DIR)/libriscv__mmachine__$(TARGET).a
	cp $< $@

$(METAL_LIB_DIR)/libmetal-gloss.a: $(METAL_LIB_DIR)/libriscv__menv__metal.a
	cp $< $@

# If we're cleaning the last Metal library for a TARGET, then remove
# the install directory, otherwise just remove the built libs for that
# CONFIGURATION.
ifeq ($(words $(wildcard $(METAL_PREFIX)/lib/*)),1)
METAL_CLEAN = $(METAL_PREFIX)
else
METAL_CLEAN = $(METAL_LIB_DIR)
endif

.PHONY: clean-metal
clean-metal:
	rm -rf $(METAL_CLEAN)
	rm -rf $(METAL_BUILD_DIR)
clean: clean-metal

metal_install: metal
	$(MAKE) -C $(METAL_SOURCE_PATH) install

执行:

make PROGRAM=sparkfun-welcome TARGET=sparkfun-redv CONFIGURATION=release upload

会执行这里的metal_install

freedom-e-sdk/bsp/sparkfun-redv/metal.default.lds

从链接脚本我们可以知道,程序的入口是_enter,以及内存的布局

OUTPUT_ARCH("riscv")
# 入口
ENTRY(_enter)
# 内存布局
MEMORY
{
	flash (rxai!w) : ORIGIN = 0x20010000, LENGTH = 0x6a120
	itim (wx!rai) : ORIGIN = 0x8000000, LENGTH = 0x2000
	ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 0x4000
}

# 设置段的属性:表示该程序段在程序运行时应该被加载
PHDRS
{
	flash PT_LOAD;
	ram PT_LOAD;
	ram_init PT_LOAD;
	itim PT_LOAD;
	itim_init PT_LOAD;
}

freedom-e-sdk/software/sparkfun-welcome/

然后我们开始看程序代码,代码很直接,就是直接执行示例的编译,

附录

内存布局

在《fe310-g002-manual-v1p0》手册中的第四章,

MEMORY
{
	flash (rxai!w) : ORIGIN = 0x20010000, LENGTH = 0x6a120
	itim (wx!rai) : ORIGIN = 0x8000000, LENGTH = 0x2000
	ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 0x4000
}
基地址 尾地址 属性 说明 备注
0x2000_0000 0x3FFF_FFFF R XC QSPI 0 Flash (512 MiB) 片外非易失性存储器
0x4000_0000 0x7FFF_FFFF   保留  
0x8000_0000 0x8000_3FFF RWX A E31 DTIM (16 KiB) 片上易变性存储器
0x8000_4000 0xFFFF_FFFF   保留  

内存属性:R-读,W-写,X-执行,C-可缓存,A-原子

PHDRS命令

该命令仅在产生ELF目标文件时有效。

ELF目标文件格式用program headers程序头(程序头内包含一个或多个segment程序段描述)来描述程序如何被载入内存。可以用objdump -p命令查看。

当在本地ELF系统运行ELF目标文件格式的程序时,系统加载器通过读取程序头信息以知道如何将程序加载到内存。要了解系统加载器如何解析程序头,请参考ELF ABI文档。

在连接脚本内不指定PHDRS命令时,连接器能够很好的创建程序头,但是有时需要更精确的描述程序头,那么PAHDRS命令就派上用场了。

注意:一旦在连接脚本内使用了PHDRS命令,那么连接器仅会创建PHDRS命令指定的信息,所以使用时须谨慎。

PHDRS命令文法

PHDRS
{
	NAME TYPE [ FILEHDR ] [ PHDRS ] [ AT ( ADDRESS ) ]
	[ FLAGS ( FLAGS ) ] ;
}
  • NAME
    • 为程序段名,此名字可以与符号名、section名、文件名重复,因为它在一个独立的名字空间内。
    • 此名字只能在SECTIONS命令内使用。
    • 一个程序段可以由多个‘可加载’的section组成。通过输出section描述的属性:PHDRS可以将输出section加入一个程序段,
    • PHDRS中的PHDRS为程序段名。在一个输出section描述内可以多次使用:PHDRS命令,也即可以将一个section加入多个程序段。
    • 如果在一个输出section描述内指定了:PHDRS属性,那么其后的输出section描述将默认使用该属性,除非它也定义了:PHDRS属性。显然当多个输出section属于同一程序段时可简化书写。
  • TYPE
    • TYPE可以是以下八种形式,
      • PT_NULL 0:表示未被使用的程序段
      • PT_LOAD 1:表示该程序段在程序运行时应该被加载
      • PT_DYNAMIC 2:表示该程序段包含动态连接信息

Newlib

newlib,因为这个涉及到裸机编程时,如何使用c运行库。

newlib是一个面向嵌入式系统的c运行库。在裸机如果想要实现c运行库,那么newlib是最好的一个选择。而且newlib可以集成到gcc交叉编译器中,在使用gcc的时候,直接链接newlib库,生成可执行文件。

Newlib由三部分构成:libgloss、libc、libm,三者在Newlib源代码中的存储位置如下

  • newlib-x.y.z
    • libgloss
    • newlib
      • libc
      • libm

libc是标准C库,libm是标准数学库,libgloss是底层库。

对于如下的代码:

#include <stdio.h>

int main() {
	printf("Hello, World!\n");
	return 0;
}

使用aarch64-none-elf-gcc工具,直接编译的话,那么会有报错信息,提示有符号找不到

aarch64-none-elf-gcc hello.c –o hello.elf

这些符号,是使用newlib库时,需要自己实现的桩函数,包括了_write_close函数等等。因为newlib库,并不知道裸机底层,是如何实现这些操作的。

这里,我们以riscv-newlib这个github上托管的开源代码库为例,来说明。该版本库的github地址为https://github.com/riscv/riscv-newlib。

以调用write函数为例,write函数,总共3个参数,第一个是文件描述符,第二个是字符串,第三个是字符串长度。

如果我们在程序中,调用了write函数,那么就会调用newlib库中的write函数。该函数实现在 newlib/libc/syscalls/syswrite.c 中。

/* connector for write */
#include <reent.h>
#include <unistd.h>
  
_READ_WRITE_RETURN_TYPE
write (int fd,
const void *buf,
    size_t cnt)
{
return _write_r (_REENT, fd, buf, cnt);
}

在write函数中,会调用_write_r函数。 而_write_r函数,也实现在newlib库中。函数实现在newlib/libc/reent/writer.c中:

_ssize_t _write_r (struct _reent *ptr,
int fd,
const void *buf,
    size_t cnt)
{
 _ssize_t ret;
  
 errno = 0;
if ((ret = (_ssize_t)_write (fd, buf, cnt)) == -1 && errno != 0)
   ptr->_errno = errno;
return ret;
}

_write_r函数,带有_r后缀,表示该函数是可重入函数,也就是无论调用多少次,结果都是一样的。该函数,内部调用了_write函数。可重入函数,实现在reent目录下。

_write函数,是没有实现在libc中的,而是在libgloss中。可以这样认为,libgloss是底层的驱动实现,而main newlib是有硬件平台无关的通用功能实现。

在libgloss下,以各个硬件平台为文件夹,进行组织的。这里我们关心aarch64。在aarch64目录中,有syscalls.c文件,里面,就实现了newlib需要的各个桩函数。

在这个文件中,我们可以看到定义了newlib需要的桩函数:

/* Forward prototypes.  */
int _system (const char *);
int _rename (const char *, const char *);
int _isatty (int);

int _write (int, char *, int);

我们来看_write函数的实现:

int _write (int fd, char *ptr, int len)
{
	int res;
	struct fdent *pfd;
	pfd = findslot (fd);
	if (pfd == NULL) {
		errno = EBADF;
		return -1;
	}
	res = _swiwrite (pfd->handle, ptr, len);
	/* Clearly an error. */
	if (res < 0) return -1;
  
	pfd->pos += len - res;
	/* We wrote 0 bytes?  Retrieve errno just in case. */
	if ((len - res) == 0) return error (0);
	return (len - res);
}

这里调用了_swiwrite函数。_swiwrite函数,实现如下:

/* fh, is a valid internal file handle.
  Returns the number of bytes *not* written. */
int _swiwrite (int fh, char *ptr, int len)
{
	param_block_t block[3];
	block[0] = fh;
	block[1] = POINTER_TO_PARAM_BLOCK_T (ptr);
	block[2] = len;
	return checkerror (do_AngelSVC (AngelSVC_Reason_Write, block));
}

在svc.h文件中,有定义相关的do_AngelSVCAngelSVC_Reason_Write的实现:

do_AngelSVC (int reason, param_block_t * arg)
{
	long long value;
	asm volatile ("mov w0, %w1; mov x1, %2; " AngelSVCInsn " %3; mov %0, x0"
		: "=r" (value) /* Outputs */
		: "r" (reason), "r" (arg), "n" (AngelSVC) /* Inputs */
		: "x0", "x1", "x2", "x3", "x17", "x30", "memory", "cc"
		/* Clobbers x0 and x1, and lr if in supervisor mode */);
	return value;
}

aarch64-none-elf-gcc -v hello.c -o hello.elf命令,可以看到输出信息里面,没有带gloss。说明链接,没有使用libgloss库。 但是如果用riscv64-unknown-elf-gcc工具编译hello.c文件,那么是可以直接成功:

riscv64-unknown-elf-gcc -v hello.c -o hello.elf

输出信息中,有gloss,说明链接,是带有libgloss库进行链接,因此链接能够成功,最终生成hello.elf。

lst文件

在使用 eclipse 进行嵌入式开发时,有时会遇到程序跳入异常服务程序的情况。

lst 文件实际是使用 objdump 反汇编 elf 文件得到的输出文件,它拥有比 map 文件更详细的信息。如果你的程序中加入了调试信息,那么你可以在 lst 中看到每一条指令的地址。借助 lst 文件,同时通过查看栈帧结构(可以通过查看相应的手册来确定栈帧的组成),通过在 lst 文件中查找 lr 的地址所在的位置,你就能立刻定位到问题。

下面是一个典型的命令行输出:

arm-none-eabi-objdump --source --all-headers --demangle --file-headers --line-numbers --wide "hello world.elf" > "hello world.lst"
  • --source: 如果可能的话,显示混入反汇编的源代码。意味着 -d
  • --all-headers:显示所有可用的头信息,包括符号表和重定位表项。使用-x等价于指定所有的-a -f -h -p -r -t
  • --demangle:将低级符号名称解码(分解)为用户级名称
    • 除了删除系统开头的任何初始下划线之外,这还使C++函数名称易于阅读。
    • 不同的编译器具有不同的处理样式
    • 可选的demangling样式参数可用于为编译器选择适当的demangling样式。
  • --file-headers:显示来自每个objfile文件的总体头的摘要信息
  • --line-nubmers:使用显示的目标代码或重定位符对应的文件名和源行号标记显示(使用调试信息)。只适用于-d、-D或-r。
  • wide:为超过80列的输出设备格式化一些行。也不要在显示符号名称时截断它们。

这个文件包含了有关编译过程的丰富信息,该文件由多个段组成,其中Symbol Listing 和 Module Information两个段对于用户分析调试程序尤其有用,下面按照各个段在

listing file中出现的先后顺序加以说明:

页头段(Page Header)

每个lst文件都有一个包含了编译器版本号、源文件名称、日期、时间、页号的头部。示例:

C51 COMPILER V7.20 MEASURE 10/01/2004 14:05:05 PAGE 1

/Users/wilson/wsFreedomStudio/red-v/src/debug/sifive-welcome.elf:     file format elf32-littleriscv
/Users/wilson/wsFreedomStudio/red-v/src/debug/sifive-welcome.elf
architecture: riscv:rv32, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x20010000