ESP8266 SDK Makefile分析
主要內容
本章我們來分析SDK中的Makefile,同時學習如何建立自己的原始碼資料夾。
預備知識
一份程式碼工程編譯成二進位制可執行檔案,需要經過四個步驟:預處理、編譯、彙編和連結。
進行編譯需要有編譯器,Linux環境下使用的是gcc,而對應的,ESP8266使用的編譯器是xtensa-lx106-elf-gcc。在後面分析Makefile中,我們就會看到該編譯器。
下面是其他一些工具的含義:
- ar:用於建立或修改備存檔案,或是從備存檔案中抽取檔案
- nm:用來列出目標檔案的符號清單
- objcopy:將目標檔案的一部分或者全部內容拷貝到另外一個目標檔案中,或者實現目標檔案的格式轉換
- objdump:檢視目標檔案或者可執行的目標檔案的構成
Makefile分析
首先我們來看看ESP8266 SDK的目錄結構以及Makefile:
│ License
│ Makefile # 主目錄下的Makefile
├─app/
│ Makefile
├─ driver
└─ user
└─ Makefile
├─bin
├─documents
├─driver_lib
├─examples
├─lib
├─include
├─ld
└─tools
由此可見,我們至少要分析三個Makefile:
- 主目錄下的Makefile
- app目錄下的Makefile
app/user/
的Makefile
主目錄下的Makefile
下面我們來hack一下主目錄下的Makefile,詳細解釋請看註釋:
# copyright (c) 2010 Espressif System # ifndef PDIR
endif
############################################ # 編譯工具配置 # ESP8266 主要使用的編譯器是 xtensa-lx106-elf-gcc
ifeq ($(COMPILE), gcc) AR = xtensa-lx106-elf-ar CC = xtensa-lx106-elf-gcc CXX = xtensa-lx106-elf-g++ NM = xtensa-lx106-elf-nm CPP = xtensa-lx106-elf-cpp OBJCOPY = xtensa-lx106-elf-objcopy OBJDUMP = xtensa-lx106-elf-objdump else AR = xt-ar CC = xt-xcc CXX = xt-xcc NM = xt-nm CPP = xt-cpp OBJCOPY = xt-objcopy OBJDUMP = xt-objdump endif
############################################ # 引數預設配置
# =none - 不使用boot # =old - 使用老版本的boot_v1.1+ # =new - 使用新版本的boot_v1.2+ BOOT?=none
# =0 - 不使用遠端升級FOTA # =1 - 使用FOTA,生成user1.<flash_map>.<BOOT>.bin # =2 - 使用FOTA,生成user2.<flash_map>.<BOOT>.bin APP?=0
# SPI速率和模式,一般不用改動 SPI_SPEED?=40 SPI_MODE?=QIO
# SPI_SIZE_MAP flash對映方式 # 4MB Flash使用 SPI_SIZE_MAP?=4 SPI_SIZE_MAP?=4
############################################
ifeq ($(BOOT), new) boot = new else ifeq ($(BOOT), old) boot = old else boot = none endif endif
ifeq ($(APP), 1) app = 1 else ifeq ($(APP), 2) app = 2 else app = 0 endif endif
ifeq ($(SPI_SPEED), 26.7) freqdiv = 1 else ifeq ($(SPI_SPEED), 20) freqdiv = 2 else ifeq ($(SPI_SPEED), 80) freqdiv = 15 else freqdiv = 0 endif endif endif
ifeq ($(SPI_MODE), QOUT) mode = 1 else ifeq ($(SPI_MODE), DIO) mode = 2 else ifeq ($(SPI_MODE), DOUT) mode = 3 else mode = 0 endif endif endif
addr = 0x01000
ifeq ($(SPI_SIZE_MAP), 1) size_map = 1 flash = 256 else ifeq ($(SPI_SIZE_MAP), 2) size_map = 2 flash = 1024 ifeq ($(app), 2) addr = 0x81000 endif else ifeq ($(SPI_SIZE_MAP), 3) size_map = 3 flash = 2048 ifeq ($(app), 2) addr = 0x81000 endif else ifeq ($(SPI_SIZE_MAP), 4) size_map = 4 flash = 4096 ifeq ($(app), 2) addr = 0x81000 endif else ifeq ($(SPI_SIZE_MAP), 5) size_map = 5 flash = 2048 ifeq ($(app), 2) addr = 0x101000 endif else ifeq ($(SPI_SIZE_MAP), 6) size_map = 6 flash = 4096 ifeq ($(app), 2) addr = 0x101000 endif else ifeq ($(SPI_SIZE_MAP), 8) size_map = 8 flash = 8192 ifeq ($(app), 2) addr = 0x101000 endif else ifeq ($(SPI_SIZE_MAP), 9) size_map = 9 flash = 16384 ifeq ($(app), 2) addr = 0x101000 endif else size_map = 0 flash = 512 ifeq ($(app), 2) addr = 0x41000 endif endif endif endif endif endif endif endif endif
############################################ # 選擇連結工具 # 這裡選擇了主目錄下的 ld/eagle.app.v6.ld LD_FILE = $(LDDIR)/eagle.app.v6.ld
# 如果boot!=none,才進入這裡 ifneq ($(boot), none) ifneq ($(app),0) ifneq ($(findstring $(size_map), 6 8 9),) LD_FILE = $(LDDIR)/eagle.app.v6.$(boot).2048.ld else ifeq ($(size_map), 5) LD_FILE = $(LDDIR)/eagle.app.v6.$(boot).2048.ld else ifeq ($(size_map), 4) LD_FILE = $(LDDIR)/eagle.app.v6.$(boot).1024.app$(app).ld else ifeq ($(size_map), 3) LD_FILE = $(LDDIR)/eagle.app.v6.$(boot).1024.app$(app).ld else ifeq ($(size_map), 2) LD_FILE = $(LDDIR)/eagle.app.v6.$(boot).1024.app$(app).ld else ifeq ($(size_map), 0) LD_FILE = $(LDDIR)/eagle.app.v6.$(boot).512.app$(app).ld endif endif endif endif endif endif BIN_NAME = user$(app).$(flash).$(boot).$(size_map) endif else app = 0 endif
############################################
CSRCS ?= $(wildcard *.c) CXXSRCS ?= $(wildcard *.cpp) ASRCs ?= $(wildcard *.s) ASRCS ?= $(wildcard *.S) SUBDIRS ?= $(patsubst %/,%,$(dir $(wildcard */Makefile)))
ODIR := .output OBJODIR := $(ODIR)/$(TARGET)/$(FLAVOR)/obj
# 設定 OBJS 變數 OBJS := $(CSRCS:%.c=$(OBJODIR)/%.o) \ $(CXXSRCS:%.cpp=$(OBJODIR)/%.o) \ $(ASRCs:%.s=$(OBJODIR)/%.o) \ $(ASRCS:%.S=$(OBJODIR)/%.o)
# 設定 DEPS 變數 DEPS := $(CSRCS:%.c=$(OBJODIR)/%.d) \ $(CXXSCRS:%.cpp=$(OBJODIR)/%.d) \ $(ASRCs:%.s=$(OBJODIR)/%.d) \ $(ASRCS:%.S=$(OBJODIR)/%.d)
# 設定 LIBODIR 庫資料夾 LIBODIR := $(ODIR)/$(TARGET)/$(FLAVOR)/lib OLIBS := $(GEN_LIBS:%=$(LIBODIR)/%)
IMAGEODIR := $(ODIR)/$(TARGET)/$(FLAVOR)/image OIMAGES := $(GEN_IMAGES:%=$(IMAGEODIR)/%)
# 設定 BINODIR 二進位制資料夾 BINODIR := $(ODIR)/$(TARGET)/$(FLAVOR)/bin OBINS := $(GEN_BINS:%=$(BINODIR)/%)
############################################################# CCFLAGS += \ -g \ -Wpointer-arith \ -Wundef \ -Werror \ -Wl,-EL \ -fno-inline-functions \ -nostdlib \ -mlongcalls \ -mtext-section-literals \ -ffunction-sections \ -fdata-sections \ -fno-builtin-printf # -Wall
DEFINES += -DSPI_FLASH_SIZE_MAP=$(size_map) CFLAGS = $(CCFLAGS) $(DEFINES) $(EXTRA_CCFLAGS) $(INCLUDES) DFLAGS = $(CCFLAGS) $(DDEFINES) $(EXTRA_CCFLAGS) $(INCLUDES)
############################################################# # Functions #
define ShortcutRule $(1): .subdirs $(2)/$(1) endef
define MakeLibrary DEP_LIBS_$(1) = $$(foreach lib,$$(filter %.a,$$(COMPONENTS_$(1))),$$(dir $$(lib))$$(LIBODIR)/$$(notdir $$(lib))) DEP_OBJS_$(1) = $$(foreach obj,$$(filter %.o,$$(COMPONENTS_$(1))),$$(dir $$(obj))$$(OBJODIR)/$$(notdir $$(obj))) $$(LIBODIR)/$(1).a: $$(OBJS) $$(DEP_OBJS_$(1)) $$(DEP_LIBS_$(1)) $$(DEPENDS_$(1)) @mkdir -p $$(LIBODIR) $$(if $$(filter %.a,$$?),mkdir -p $$(EXTRACT_DIR)_$(1)) $$(if $$(filter %.a,$$?),cd $$(EXTRACT_DIR)_$(1); $$(foreach lib,$$(filter %.a,$$?),$$(AR) xo $$(UP_EXTRACT_DIR)/$$(lib);)) $$(AR) ru [email protected] $$(filter %.o,$$?) $$(if $$(filter %.a,$$?),$$(EXTRACT_DIR)_$(1)/*.o) $$(if $$(filter %.a,$$?),$$(RM) -r $$(EXTRACT_DIR)_$(1)) endef
define MakeImage DEP_LIBS_$(1) = $$(foreach lib,$$(filter %.a,$$(COMPONENTS_$(1))),$$(dir $$(lib))$$(LIBODIR)/$$(notdir $$(lib))) DEP_OBJS_$(1) = $$(foreach obj,$$(filter %.o,$$(COMPONENTS_$(1))),$$(dir $$(obj))$$(OBJODIR)/$$(notdir $$(obj))) $$(IMAGEODIR)/$(1).out: $$(OBJS) $$(DEP_OBJS_$(1)) $$(DEP_LIBS_$(1)) $$(DEPENDS_$(1)) @mkdir -p $$(IMAGEODIR) $$(CC) $$(LDFLAGS) $$(if $$(LINKFLAGS_$(1)),$$(LINKFLAGS_$(1)),$$(LINKFLAGS_DEFAULT) $$(OBJS) $$(DEP_OBJS_$(1)) $$(DEP_LIBS_$(1))) -o [email protected] endef
$(BINODIR)/%.bin: $(IMAGEODIR)/%.out @mkdir -p $(BINODIR) ifeq ($(APP), 0) @$(RM) -r ../bin/eagle.S ../bin/eagle.dump @$(OBJDUMP) -x -s $< > ../bin/eagle.dump @$(OBJDUMP) -S $< > ../bin/eagle.S else mkdir -p ../bin/upgrade @$(RM) -r ../bin/upgrade/$(BIN_NAME).S ../bin/upgrade/$(BIN_NAME).dump @$(OBJDUMP) -x -s $< > ../bin/upgrade/$(BIN_NAME).dump @$(OBJDUMP) -S $< > ../bin/upgrade/$(BIN_NAME).S endif
@$(OBJCOPY) --only-section .text -O binary $< eagle.app.v6.text.bin @$(OBJCOPY) --only-section .data -O binary $< eagle.app.v6.data.bin @$(OBJCOPY) --only-section .rodata -O binary $< eagle.app.v6.rodata.bin @$(OBJCOPY) --only-section .irom0.text -O binary $< eagle.app.v6.irom0text.bin
@echo "" @echo "!!!"
# 預設是 app==0 # 下面是輸出列印資訊 ifeq ($(app), 0) @python ../tools/gen_appbin.py $< 0 $(mode) $(freqdiv) $(size_map) $(app) @mv eagle.app.flash.bin ../bin/eagle.flash.bin @mv eagle.app.v6.irom0text.bin ../bin/eagle.irom0text.bin @rm eagle.app.v6.* @echo "No boot needed." @echo "Generate eagle.flash.bin and eagle.irom0text.bin successully in folder bin." @echo "eagle.flash.bin-------->0x00000" @echo "eagle.irom0text.bin---->0x10000" else ifneq ($(boot), new) @python ../tools/gen_appbin.py $< 1 $(mode) $(freqdiv) $(size_map) $(app) @echo "Support boot_v1.1 and +" else @python ../tools/gen_appbin.py $< 2 $(mode) $(freqdiv) $(size_map) $(app)
ifeq ($(size_map), 6) @echo "Support boot_v1.4 and +" else ifeq ($(size_map), 5) @echo "Support boot_v1.4 and +" else @echo "Support boot_v1.2 and +" endif endif endif
@mv eagle.app.flash.bin ../bin/upgrade/$(BIN_NAME).bin @rm eagle.app.v6.* @echo "Generate $(BIN_NAME).bin successully in folder bin/upgrade." @echo "boot.bin------------>0x00000" @echo "$(BIN_NAME).bin--->$(addr)" endif
@echo "!!!"
############################################################# # Rules base # Should be done in top-level makefile only #
############################################ # make all執行的方法 all: .subdirs $(OBJS) $(OLIBS) $(OIMAGES) $(OBINS) $(SPECIAL_MKTARGETS)
############################################ # make clean執行的方法 clean: $(foreach d, $(SUBDIRS), $(MAKE) -C $(d) clean;) $(RM) -r $(ODIR)/$(TARGET)/$(FLAVOR)
clobber: $(SPECIAL_CLOBBER) $(foreach d, $(SUBDIRS), $(MAKE) -C $(d) clobber;) $(RM) -r $(ODIR)
.subdirs: @set -e; $(foreach d, $(SUBDIRS), $(MAKE) -C $(d);)
#.subdirs: # $(foreach d, $(SUBDIRS), $(MAKE) -C $(d))
ifneq ($(MAKECMDGOALS),clean) ifneq ($(MAKECMDGOALS),clobber) ifdef DEPS sinclude $(DEPS) endif endif endif
# “$<”表示所有的依賴目標集(所有.c字尾檔案),“[email protected]”表示目標集(所有.o字尾檔案)
$(OBJODIR)/%.o: %.c @mkdir -p $(OBJODIR); $(CC) $(if $(findstring $<,$(DSRCS)),$(DFLAGS),$(CFLAGS)) $(COPTS_$(*F)) -o [email protected] -c $<
$(OBJODIR)/%.d: %.c @mkdir -p $(OBJODIR); @echo DEPEND: $(CC) -M $(CFLAGS) $< @set -e; rm -f [email protected]; \ $(CC) -M $(CFLAGS) $< > [email protected]$$$$; \ sed 's,\($*\.o\)[ :]*,$(OBJODIR)/\1 [email protected] : ,g' < [email protected]$$$$ > [email protected]; \ rm -f [email protected]$$$$
$(OBJODIR)/%.o: %.cpp @mkdir -p $(OBJODIR); $(CXX) $(if $(findstring $<,$(DSRCS)),$(DFLAGS),$(CFLAGS)) $(COPTS_$(*F)) -o [email protected] -c $<
$(OBJODIR)/%.d: %.cpp @mkdir -p $(OBJODIR); @echo DEPEND: $(CXX) -M $(CFLAGS) $< @set -e; rm -f [email protected]; \ sed 's,\($*\.o\)[ :]*,$(OBJODIR)/\1 [email protected] : ,g' < [email protected]$$$$ > [email protected]; \ rm -f [email protected]$$$$
$(OBJODIR)/%.o: %.s @mkdir -p $(OBJODIR); $(CC) $(CFLAGS) -o [email protected] -c $<
$(OBJODIR)/%.d: %.s @mkdir -p $(OBJODIR); \ set -e; rm -f [email protected]; \ $(CC) -M $(CFLAGS) $< > [email protected]$$$$; \ sed 's,\($*\.o\)[ :]*,$(OBJODIR)/\1 [email protected] : ,g' < [email protected]$$$$ > [email protected]; \ rm -f [email protected]$$$$
$(OBJODIR)/%.o: %.S @mkdir -p $(OBJODIR); $(CC) $(CFLAGS) -D__ASSEMBLER__ -o [email protected] -c $<
$(OBJODIR)/%.d: %.S @mkdir -p $(OBJODIR); \ set -e; rm -f [email protected]; \ $(CC) -M $(CFLAGS) $< > [email protected]$$$$; \ sed 's,\($*\.o\)[ :]*,$(OBJODIR)/\1 [email protected] : ,g' < [email protected]$$$$ > [email protected]; \ rm -f [email protected]$$$$
$(foreach lib,$(GEN_LIBS),$(eval $(call ShortcutRule,$(lib),$(LIBODIR))))
$(foreach image,$(GEN_IMAGES),$(eval $(call ShortcutRule,$(image),$(IMAGEODIR))))
$(foreach bin,$(GEN_BINS),$(eval $(call ShortcutRule,$(bin),$(BINODIR))))
$(foreach lib,$(GEN_LIBS),$(eval $(call MakeLibrary,$(basename $(lib)))))
$(foreach image,$(GEN_IMAGES),$(eval $(call MakeImage,$(basename $(image)))))
############################################################# # Recursion Magic - Don't touch this!! # # Each subtree potentially has an include directory # corresponding to the common APIs applicable to modules # rooted at that subtree. Accordingly, the INCLUDE PATH # of a module can only contain the include directories up # its parent path, and not its siblings # # Required for each makefile to inherit from the parent #
INCLUDES := $(INCLUDES) -I $(PDIR)include -I $(PDIR)include/$(TARGET) -I $(PDIR)driver_lib/include PDIR := ../$(PDIR) sinclude $(PDIR)Makefile
app下的Makefile
下面我們就來hack一下app/Makefile
############################################################# # Required variables for each makefile # Discard this section from all parent makefiles # Expected variables (with automatic defaults): # CSRCS (all "C" files in the dir) # SUBDIRS (all subdirs with a Makefile) # GEN_LIBS - list of libs to be generated () # GEN_IMAGES - list of object file images to be generated () # GEN_BINS - list of binaries to be generated () # COMPONENTS_xxx - a list of libs/objs in the form # subdir/lib to be extracted and rolled up into # a generated lib/image xxx.a () # TARGET = eagle ############################################################# # 選擇debug工程還是release #FLAVOR = release FLAVOR = debug
#EXTRA_CCFLAGS += -u
ifndef PDIR # { GEN_IMAGES= eagle.app.v6.out GEN_BINS= eagle.app.v6.bin SPECIAL_MKTARGETS=$(APP_MKTARGETS)
############################################################# # 子目錄,如果你在app裡新建了一個原始檔目錄,需要新增到這裡 # 否則不會編譯未新增的目錄的原始檔! SUBDIRS= \ user
# 比如添加了一個 network 資料夾,則: #SUBDIRS= \ # user \ # driver \ # network endif # } PDIR
APPDIR = . LDDIR = ../ld
CCFLAGS += -Os
TARGET_LDFLAGS = \ -nostdlib \ -Wl,-EL \ --longcalls \ --text-section-literals
############################################################# # 根據 FLAVOR 變數選擇優化等級 # -O0不優化,-O2中級優化 ifeq ($(FLAVOR),debug) TARGET_LDFLAGS += -g -O2 endif
ifeq ($(FLAVOR),release) TARGET_LDFLAGS += -g -O0 endif
############################################################# # 新增靜態連結庫,對應前面 SUBDIRS 變數設定的目錄 # 每一個資料夾裡的原始碼先編譯成對應的靜態庫,然後在進行連結 # 所以如果新增新的原始碼資料夾,也需要在這裡新增對應的靜態庫! COMPONENTS_eagle.app.v6 = \ user/libuser.a
# 比如新增network: #COMPONENTS_eagle.app.v6 = \ # user/libuser.a \ # driver/libdriver.a \ # network/libnetwork.a
############################################################# # 連結引數 # 參考lib資料夾裡的靜態連結庫 # 比如,如果需要使用smartconfig介面 # 只需要新增 -lsmartconfig 即可。 LINKFLAGS_eagle.app.v6 = \ -L../lib \ -nostdlib \ -T$(LD_FILE) \ -Wl,--no-check-sections \ -Wl,--gc-sections \ -u call_user_start \ -Wl,-static \ -Wl,--start-group \ -lc \ -lgcc \ -lhal \ -lphy \ -lpp \ -lnet80211 \ -llwip \ -lwpa \ -lcrypto \ -lmain \ -ljson \ -lssl \ -lupgrade \ -lsmartconfig \ -lairkiss\ $(DEP_LIBS_eagle.app.v6) \ -Wl,--end-group
DEPENDS_eagle.app.v6 = \ $(LD_FILE) \ $(LDDIR)/eagle.rom.addr.v6.ld
############################################################# # Configuration i.e. compile options etc. # Target specific stuff (defines etc.) goes in here! # Generally values applying to a tree are captured in the # makefile at its root level - these are then overridden # for a subtree within the makefile rooted therein #
#UNIVERSAL_TARGET_DEFINES = \
# Other potential configuration flags include: # -DTXRX_TXBUF_DEBUG # -DTXRX_RXBUF_DEBUG # -DWLAN_CONFIG_CCX CONFIGURATION_DEFINES = -DICACHE_FLASH
DEFINES += \ $(UNIVERSAL_TARGET_DEFINES) \ $(CONFIGURATION_DEFINES)
DDEFINES += \ $(UNIVERSAL_TARGET_DEFINES) \ $(CONFIGURATION_DEFINES)
############################################################# # Recursion Magic - Don't touch this!! # # Each subtree potentially has an include directory # corresponding to the common APIs applicable to modules # rooted at that subtree. Accordingly, the INCLUDE PATH # of a module can only contain the include directories up # its parent path, and not its siblings # # Required for each makefile to inherit from the parent #
############################################################# # 把當前目錄下的 include 資料夾包含進來 INCLUDES := $(INCLUDES) -I $(PDIR)include
# 把子目錄包含進來 PDIR := ../$(PDIR)
# 把子目錄下的Makefile加進來 # sinclude:以忽略錯誤的方式執行 sinclude $(PDIR)Makefile
.PHONY: FORCE FORCE:
app/user/
下的Makefile
由app/Makefile
可知,Makefile最後會把子目錄下的Makefile加進來,而且好像還添加了user/libuser.a
和driver/libdriver.a
,但是這些.a
檔案怎麼生成我們還不知道。
所以我們來看下app/user/
下的Makefile做了一些什麼事情。
############################################################# # Required variables for each makefile # Discard this section from all parent makefiles # Expected variables (with automatic defaults): # CSRCS (all "C" files in the dir) # SUBDIRS (all subdirs with a Makefile) # GEN_LIBS - list of libs to be generated () # GEN_IMAGES - list of images to be generated () # COMPONENTS_xxx - a list of libs/objs in the form # subdir/lib to be extracted and rolled up into # a generated lib/image xxx.a () #
# 仔細閱讀上面英文即可知道是什麼意思了 # 下面語句即是生成 libuser.a ifndef PDIR GEN_LIBS = libuser.a endif
############################################################# # Configuration i.e. compile options etc. # Target specific stuff (defines etc.) goes in here! # Generally values applying to a tree are captured in the # makefile at its root level - these are then overridden # for a subtree within the makefile rooted therein # #DEFINES +=
############################################################# # Recursion Magic - Don't touch this!! # # Each subtree potentially has an include directory # corresponding to the common APIs applicable to modules # rooted at that subtree. Accordingly, the INCLUDE PATH # of a module can only contain the include directories up # its parent path, and not its siblings # # Required for each makefile to inherit from the parent #
# 查詢標頭檔案時,進入當前資料夾的 include 資料夾進行查詢(如果有) INCLUDES := $(INCLUDES) -I $(PDIR)include
# 在當前資料夾的進行查詢 INCLUDES += -I ./
# 在主目錄下的 include/ets/ 資料夾進行查詢 # (實際上本人都沒有找到 include/ets/ 資料夾) INCLUDES += -I ../../include/ets
PDIR := ../$(PDIR) sinclude $(PDIR)Makefile
如何新添原始碼資料夾
- 在
app/
目錄下新建一個資料夾,比如叫network
; - 然後把
app/user/Makefile
檔案拷貝過去,修改下面語句
ifndef PDIR
GEN_LIBS = libuser.a
endif
改為:
ifndef PDIR
GEN_LIBS = libnetwork.a
endif
3.然後開啟app/Makefile
,給SUBDIRS
變數和COMPONENTS_eagle.app.v6
新增對應的檔案即可(app/Makefile
程式碼分析小節已經有說明);
4.最後編譯的時候,network
資料夾下的原始碼即會被編譯進來了。
參考資料
打廣告時間~下面是本人做ESP8266專案收集的各種示例工程,已經放在Github: