00 NDS makefile 및 NDS 파일 생성 과정 분석
원본 : http://kkamagui.springnote.com/pages/415876
들어가기 전에...
- 이 글은 kkamagui에 의해 작성된 글입니다.
- 마음껏 인용하시거나 사용하셔도 됩니다. 단 출처(http://kkamagui.tistory.com, http://kkamagui.springnote.com)는 밝혀 주십시오.
- 기타 사항은 mint64os at gmail.com 이나 http://kkamagui.tistory.com으로 보내주시면 반영하겠습니다.
- 상세한 내용은 책 "64비트 멀티코어 OS 구조와 원리"를 참고하기 바랍니다.
0,시작하면서...
새로운 프로젝트를 생성하고 소스파일을 생성한 후 컴파일 및 링크를 거치면 *.nds 파일이 생기게 된다. C/CPP 파일을 이용해서 어떻게 NDS 롬 파일 형태를 만드는 걸까?
소스 파일에서 NDS 파일이 만들어지기까지 아래의 단계를 거치게 된다.
- c/cpp 파일을 컴파일 하여 .o 파일 생성
- .o 파일을 링크하여 .elf 파일 생성
- .elf 파일을 objcopy 프로그램으로 binary 포맷으로 변경, .arm9/.arm7 파일 생성
- .arm9/.arm7 파일을 ndstool.exe 프로그램으로 함께 묶어 .nds 파일 생성
위의 과정을 확실히 알아보기 위해서는 소스가 컴파일 및 링크되는 규칙이 담긴 makefile을 분석해야 한다.
1.makefile 분석
makefile은 아래와 같은 내용으로 되어있다
- #---------------------------------------------------------------------------------
.SUFFIXES:
#--------------------------------------------------------------------------------- - ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM)
endif - include $(DEVKITARM)/ds_rules
- #---------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# INCLUDES is a list of directories containing extra header files
#---------------------------------------------------------------------------------
TARGET := $(shell basename $(CURDIR))
BUILD := build
SOURCES := gfx source data
INCLUDES := include build - #---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -mthumb -mthumb-interwork - # note: arm9tdmi isn't the correct CPU arch, but anything newer and LD
# *insists* it has a FPU or VFP, and it won't take no for an answer!
CFLAGS := -g -Wall -O2\
-mcpu=arm9tdmi -mtune=arm9tdmi -fomit-frame-pointer\
-ffast-math \
$(ARCH) - CFLAGS += $(INCLUDE) -DARM9
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions - ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=ds_arm9.specs -g $(ARCH) -mno-fpu -Wl,-Map,$(notdir $*.map) - #---------------------------------------------------------------------------------
# any extra libraries we wish to link with the project
#---------------------------------------------------------------------------------
LIBS := -lfat -lnds9 <== -lfat는 libfat를 사용하기 위함이다.
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(LIBNDS)
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD) - CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.bin)))
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#--------------------------------------------------------------------------------- - export OFILES := $(BINFILES:.bin=.o) \
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
.PHONY: $(BUILD) clean
#---------------------------------------------------------------------------------
$(BUILD):
@[ -d $@ ] || mkdir -p $@ <== 확실한 뜻은 모르겠으나 $(BUILD) 디렉토리가 생성되어있거나 디렉토리 만들어라 인것 같음
@make --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).elf $(TARGET).nds $(TARGET).arm9 $(TARGET).ds.gba
#---------------------------------------------------------------------------------
else
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
$(OUTPUT).nds : $(OUTPUT).arm9
$(OUTPUT).arm9 : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
#---------------------------------------------------------------------------------
%.o : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------
make에서 사용되는 괴 문자들이 많이 보이는데, 괴문자에 대한 자세한 내용은 02 간단한 Make 사용법에서 보도록하고 $@는 ':' 의 좌측을 의미하고 $<는 우측을 의미한다는 것 정도만 알아두자.
재미있는 점은 코드가 thumb 모드로 생성된다는 것이다. thumb 모드는 32bit 명령이 16bit로 줄어든 형태로써 코드의 크기를 30% 정도 더 줄일 수 있는 장점이 있다.
include하여 사용하고 있는 ds_rules 파일을 살펴보자. ds_rules 파일은 devkitARM 폴더 아래에 있고 아래와 같은 내용을 담고 있다.
- ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>devkitPro)
endif - include $(DEVKITARM)/base_rules
- LIBNDS := $(DEVKITPRO)/libnds
- #---------------------------------------------------------------------------------
%.ds.gba: %.nds
@dsbuild $<
@echo built ... $(notdir $@) - #---------------------------------------------------------------------------------
%.nds: %.arm9
@ndstool -c $@ -9 $<
@echo built ... $(notdir $@) - #---------------------------------------------------------------------------------
%.arm9: %.elf
@$(OBJCOPY) -O binary $< $@
@echo built ... $(notdir $@)
#---------------------------------------------------------------------------------
%.arm7: %.elf
@$(OBJCOPY) -O binary $< $@
@echo built ... $(notdir $@) - #---------------------------------------------------------------------------------
%.elf:
@echo linking $(notdir $@)
@$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
어떻게 하여 NDS 파일을 만드는지 위를 보면 확실히 알 수 있다 rule에 의해서 elf -> arm9 -> nds -> ds.gba 파일이 만들어지는 것이다. 그럼 ds_rules에서 inlcude하는 base_rules는 어떻게 구성되어있을까?
아래는 base_rules 내용이다.
- #---------------------------------------------------------------------------------
# path to tools - this can be deleted if you set the path in windows
#---------------------------------------------------------------------------------
export PATH := $(DEVKITARM)/bin:$(PATH) - #---------------------------------------------------------------------------------
# the prefix on the compiler executables
#---------------------------------------------------------------------------------
PREFIX := arm-eabi- - export CC := $(PREFIX)gcc
export CXX := $(PREFIX)g++
export AS := $(PREFIX)as
export AR := $(PREFIX)ar
export OBJCOPY := $(PREFIX)objcopy - #---------------------------------------------------------------------------------
%.a:
#---------------------------------------------------------------------------------
@echo $(notdir $@)
@rm -f $@
$(AR) -rc $@ $^ - #---------------------------------------------------------------------------------
%.arm.o: %.arm.cpp
@echo $(notdir $<)
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) -marm-c $< -o $@
#---------------------------------------------------------------------------------
%.arm.o: %.arm.c
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) -marm -c $< -o $@ - #---------------------------------------------------------------------------------
%.iwram.o: %.iwram.cpp
@echo $(notdir $<)
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) -marm -mlong-calls -c $< -o $@
#---------------------------------------------------------------------------------
%.iwram.o: %.iwram.c
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) -marm -mlong-calls -c $< -o $@ - #---------------------------------------------------------------------------------
%.itcm.o: %.itcm.cpp
@echo $(notdir $<)
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) -marm -mlong-calls -c $< -o $@
#---------------------------------------------------------------------------------
%.itcm.o: %.itcm.c
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) -marm -mlong-calls -c $< -o $@ - #---------------------------------------------------------------------------------
%.o: %.cpp
@echo $(notdir $<)
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) -c $< -o $@
#---------------------------------------------------------------------------------
%.o: %.c
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) -c $< -o $@
#---------------------------------------------------------------------------------
%.o: %.s
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@- #---------------------------------------------------------------------------------
%.o: %.S
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@ - #---------------------------------------------------------------------------------
# canned command sequence for binary data
#---------------------------------------------------------------------------------
define bin2o
bin2s $< | $(AS) $(ARCH) -o $(@)
echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(<F) | tr . _)`.h
echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(<F) | tr . _)`.h
echo "extern const u32" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(<F) | tr . _)`.h
endef
각 파일의 확장자에 따라서 어떻게 빌드해야 할지에 대한 구체적인 내용이 나와있다. 그럼 이 make 파일을 이용하여 build를 하면 컴파일 되고 적당히 링크되어 1차적으로 elf 파일이 생성될 것이다.
컴파일해서 나온 .o 파일은 별다른 의문이 없지만 링크되어 나온 elf 파일은 조금 생각해 봐야 한다. objcopy로 elf 파일을 binary 형태로 변화시키는데, 그렇다면 elf 파일도 NDS 롬 파일 형태에 맞도록 뭔가 달라져야 할 것이 아닌가? 링크는 LDFLAG 옵션에 들어있는 ds_arm9.specs 을 참고하도록 되어있으니 Linker 쪽을 살펴보자.
2. 링커 스크립트(Linker Script) 분석
ds_arm9.specs 파일은 devkitARM\arm-eabi\lib 폴더 아래에서 살펴볼 수 있다.
- %rename link old_link
%rename link_gcc_c_sequence old_gcc_c_sequence - *link_gcc_c_sequence:
%(old_gcc_c_sequence) --start-group -lsysbase -lc --end-group - *link:
%(old_link) -T ds_arm9.ld%s - *startfile:
ds_arm9_crt0%O%s crti%O%s crtbegin%O%s
구체적인 내용은 확실히 모르겠지만 일단 ds_arm9.ld 파일을 참조하고 ds_arm9_crt, crti, crtbegin 으로 시작하는 파일과 관계가 있음을 유추할 수 있다. ds_arm9.ld 파일을 한번 살펴보면 아래와 같다.
- OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start) - MEMORY {
- rom : ORIGIN = 0x08000000, LENGTH = 32M
ewram : ORIGIN = 0x02000000, LENGTH = 4M - 4k
dtcm : ORIGIN = 0x0b000000, LENGTH = 16K
itcm : ORIGIN = 0x01000000, LENGTH = 32K
} - __itcm_start = ORIGIN(itcm);
__ewram_end = ORIGIN(ewram) + LENGTH(ewram);
__eheap_end = ORIGIN(ewram) + LENGTH(ewram);
__dtcm_start = ORIGIN(dtcm);
__dtcm_top = ORIGIN(dtcm) + LENGTH(dtcm);
__irq_flags = __dtcm_top - 0x08;
__irq_vector = __dtcm_top - 0x04; - __sp_svc = __dtcm_top - 0x100;
__sp_irq = __sp_svc - 0x100;
__sp_usr = __sp_irq - 0x100; - SECTIONS
{
.init :
{
__text_start = . ;
KEEP (*(.init))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >ewram = 0xff - .plt : { *(.plt) } >ewram = 0xff
- .text : /* ALIGN (4): */
{
*(EXCLUDE_FILE (*.itcm*) .text) - *(.text.*)
*(.stub)
/* .gnu.warning sections are handled specially by elf32.em. */
*(.gnu.warning)
*(.gnu.linkonce.t*)
*(.glue_7)
*(.glue_7t)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >ewram = 0xff - .fini :
{
KEEP (*(.fini))
} >ewram =0xff - __text_end = . ;
- .rodata :
{
*(.rodata)
*all.rodata*(*)
*(.roda)
*(.rodata.*)
*(.gnu.linkonce.r*)
SORT(CONSTRUCTORS)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >ewram = 0xff - .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >ewram
__exidx_start = .;
.ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } >ewram
__exidx_end = .;
/* Ensure the __preinit_array_start label is properly aligned. We
could instead move the label definition inside the section, but
the linker would then create the section even if it turns out to
be empty, which isn't pretty. */
. = ALIGN(32 / 8);
PROVIDE (__preinit_array_start = .);
.preinit_array : { KEEP (*(.preinit_array)) } >ewram = 0xff
PROVIDE (__preinit_array_end = .);
PROVIDE (__init_array_start = .);
.init_array : { KEEP (*(.init_array)) } >ewram = 0xff
PROVIDE (__init_array_end = .);
PROVIDE (__fini_array_start = .);
.fini_array : { KEEP (*(.fini_array)) } >ewram = 0xff
PROVIDE (__fini_array_end = .); - .ctors :
{
/* gcc uses crtbegin.o to find the start of the constructors, so
we make sure it is first. Because this is a wildcard, it
doesn't matter if the user does not actually link against
crtbegin.o; the linker won't look for a file to match a
wildcard. The wildcard also means that it doesn't matter which
directory crtbegin.o is in. */
KEEP (*crtbegin.o(.ctors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >ewram = 0xff - .dtors :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >ewram = 0xff - .eh_frame :
{
KEEP (*(.eh_frame))
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >ewram = 0xff - .gcc_except_table :
{
*(.gcc_except_table)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >ewram = 0xff
.jcr : { KEEP (*(.jcr)) } >ewram = 0
.got : { *(.got.plt) *(.got) *(.rel.got) } >ewram = 0 - .ewram ALIGN(4) :
{
__ewram_start = ABSOLUTE(.) ;
*(.ewram)
*ewram.*(.text)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >ewram = 0xff
.data ALIGN(4) :
{
__data_start = ABSOLUTE(.);
*(.data)
*(.data.*)
*(.gnu.linkonce.d*)
CONSTRUCTORS
. = ALIGN(4);
__data_end = ABSOLUTE(.) ;
} >ewram = 0xff
__dtcm_lma = . ;- .dtcm __dtcm_start : AT (__dtcm_lma)
{
*(.dtcm)
*(.dtcm.*)
. = ALIGN(4);
__dtcm_end = ABSOLUTE(.);
} >dtcm = 0xff
__itcm_lma = __dtcm_lma + SIZEOF(.dtcm);- .itcm __itcm_start : AT (__itcm_lma)
{
*(.itcm)
*itcm.*(.text)
. = ALIGN(4);
__itcm_end = ABSOLUTE(.);
} >itcm = 0xff
.sbss __dtcm_end :
{
__sbss_start = ABSOLUTE(.);
__sbss_start__ = ABSOLUTE(.);
*(.sbss)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
__sbss_end = ABSOLUTE(.);
} >dtcm
__bss_lma = __itcm_lma + SIZEOF(.itcm) ;
__appended_data = __itcm_lma + SIZEOF(.itcm) ;
.bss __bss_lma : AT (__bss_lma)
{
__bss_start = ABSOLUTE(.);
__bss_start__ = ABSOLUTE(.);
*(.dynbss)
*(.gnu.linkonce.b*)
*(.bss*)
*(COMMON)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
__bss_end = ABSOLUTE(.) ;
__bss_end__ = __bss_end ;
} >ewram
_end = . ;
__end__ = . ;
PROVIDE (end = _end);- /* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
/* DWARF debug sections.
Symbols in the DWARF debugging sections are relative to the beginning
of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
.stack 0x80000 : { _stack = .; *(.stack) }
/* These must appear regardless of . */
}
위에서 보면 링크에 관련된 온갖 정보들이 다 들어있음을 알 수 있다. 주의깊게 볼 부분은 파란색 부분인데, 이 부분의 주소값들이 다 NDS의 메모리맵과 관계가 있다. 링크 스크립트에 대한 내용은 06 링커 스크립트(Linker Scrpit) 페이지를 참조하도록 하고 위에서 계속 반복해서 나오는 섹션에 대한 정보만 간단히 알아보자.
- .text : /* ALIGN (4): */
{
*(EXCLUDE_FILE (*.itcm*) .text) - *(.text.*)
- *(.stub)
/* .gnu.warning sections are handled specially by elf32.em. */
*(.gnu.warning)
*(.gnu.linkonce.t*)
*(.glue_7)
*(.glue_7t)
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
} >ewram = 0xff
>ewram = 0xff 의 뜻은 .text 영역의 섹션을 위에서 정의했던 ewram 영역에 밀어넣되, .text 섹션의 값은 0xff 값으로 초기화 하라는 것이다. 즉 ewram 영역에 .text 영역이 생길꺼고 그 영역은 0xff로 체워진다. sbss 및 bss 영역 역시 ewram에 위치하는 걸 봐서 데이터도 모두 ewram 영역에 위치하므로, 너무 큰 배열이나 값들의 연속은 메모리를 지나치게 사용할 우려가 있으므로 주의해야 한다.
위에서 보면 모든 많은 섹션들이 >ewram으로 표시된 것을 보아 거의 ewram 영역인 메인 메모리에 올라감을 알 수 있다. 그렇다면 ROM과 같은 GBA 카드리지 영역은 안쓰는 걸까? 현재( 2007/08/16 21:59:18 )까지 분석된 결과로는 거의 안쓰이는 것 같다.
3.map 파일 분석
위에서 보았던 링크 스크립트 파일을 이용하여 링커가 elf 파일을 생성하게 된다. 이때 생성된 elf 파일에 구체적인 데이터는 map 파일을 열어보면 알 수 있다. 아래는 map 파일의 내용을 간단히 추린 것이다.
- Memory Configuration
- Name Origin Length Attributes
rom 0x08000000 0x02000000
ewram 0x02000000 0x003ff000
dtcm 0x0b000000 0x00004000
itcm 0x01000000 0x00008000
*default* 0x00000000 0xffffffff - Linker script and memory map
- 0x01000000 __itcm_start = 0x1000000
0x023ff000 __ewram_end = 0x23ff000
0x023ff000 __eheap_end = 0x23ff000
0x0b000000 __dtcm_start = 0xb000000
0x0b004000 __dtcm_top = 0xb004000
0x0b003ff8 __irq_flags = (__dtcm_top - 0x8)
0x0b003ffc __irq_vector = (__dtcm_top - 0x4)
0x0b003f00 __sp_svc = (__dtcm_top - 0x100)
0x0b003e00 __sp_irq = (__sp_svc - 0x100)
0x0b003d00 __sp_usr = (__sp_irq - 0x100) - .init 0x02000000 0x22c
0x02000000 __text_start = .
*(.init)
.init 0x02000000 0x220 d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/../../../../arm-eabi/lib/thumb/ds_arm9_crt0.o
0x02000000 _start
.init 0x02000220 0x4 d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/thumb/crti.o
0x02000220 _init
.init 0x02000224 0x8 d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/thumb/crtn.o
0x0200022c . = ALIGN (0x4) - .plt
*(.plt) - .text 0x02000240 0x1cae0
*(EXCLUDE_FILE(*.itcm*) .text)
.text 0x02000240 0x0 d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/../../../../arm-eabi/lib/thumb/ds_arm9_crt0.o
.text 0x02000240 0x0 d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/thumb/crti.o
.text 0x02000240 0x70 d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/thumb/crtbegin.o
.text 0x020002b0 0x178 2DRaster.o
0x020002b0 DrawLine(void*, int, int, int, int, unsigned short)
0x0200041c DrawPixel(void*, int, int, unsigned short)
0x02000370 DrawBox(void*, int, int, int, int, unsigned short, bool)
.text 0x02000428 0x124 BootStub.o
0x020004d4 InitVideoMode()
0x02000428 SetIrq()
0x02000474 IrqHandler() - ........................
- .data 0x0201ff64 0x138e0
0x0201ff64 __data_start = <code 342> (.)
*(.data)
.data 0x0201ff64 0x0 d:/ndsl_develo - .data 0x0201ff64 0x4 d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/thumb/crtbegin.o
0x0201ff64 __dso_handle
.data 0x0201ff68 0x0 2DRaster.o
.data 0x0201ff68 0x0 BootStub.o
.data 0x0201ff68 0x0 DC.o
.data 0x0201ff68 0x0 DrawingWindow.o
.data 0x0201ff68 0x0 FileManager.o
.data 0x0201ff68 0x12c20 Font.o
0x0201ff68 g_vusHanFont - ........................
- .bss 0x020339d4 0x18e68 load address 0x020339d4
0x020339d4 __bss_start = <code 342> (.)
0x020339d4 __bss_start__ = <code 342> (.)
*(.dynbss)
*(.gnu.linkonce.b*)
*(.bss*)
.bss 0x020339d4 0x0 d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/../../../../arm-eabi/lib/thumb/ds_arm9_crt0.o
.bss 0x020339d4 0x0 d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/thumb/crti.o
.bss 0x020339d4 0x1c d:/ndsl_develop/devkitpro/devkitarm/bin/../lib/gcc/arm-eabi/4.1.1/thumb/crtbegin.o
.bss 0x020339f0 0x0 2DRaster.o
.bss 0x020339f0 0x3 BootStub.o
0x020339f2 g_ucIPCCount
0x020339f1 g_ucKeysCount
0x020339f0 g_ucVBlankCount
.bss 0x020339f3 0x0 DC.o
.bss 0x020339f3 0x0 DrawingWindow.o
.bss 0x020339f3 0x0 FileManager.o
.bss 0x020339f3 0x0 Font.o
.bss 0x020339f3 0x0 InformationWindow.o
*fill* 0x020339f3 0x1 00
.bss 0x020339f4 0x18568 Main.o
0x020339f4 g_clInformationWindow
0x02033b60 g_clBackGroundWindow
map 파일의 내용들을 보면 위와 같은 내용을 확인할 수 있는다. 우리가 작성한 코드는 .text 영역에 있으며, 우리가 사용한 변수들은 .bss 영역에 있는 것을 확인할 수 있다. 그리고 초기화 된 데이터 같은 경우(g_vusHanFont)는 .data에 존재하는 것을 발견할 수 있다.
4.Objcopy 분석
일단 컴파일 및 링크의 결과로 elf 파일이 생성되었음을 알았다. 그럼 objcopy를 통해 binary 파일로 만들면 어떤 일이 발생하는 걸까? objcopy에 대한 내용은 http://www.gnu.org/software/binutils/manual/html_chapter/binutils_3.html 에서 찾을 수 있다. 내용을 조금 살피다 보면 -O binary 옵션에 대한 내용을 찾을 수 있는데 아래와 같다.
objcopy can be used to generate a raw binary file by using an output target of `binary' (e.g., use `-O binary'). When objcopy generates a raw binary file, it will essentially produce a memory dump of the contents of the input object file. All symbols and relocation information will be discarded. The memory dump will start at the load address of the lowest section copied into the output file.
각 섹션들의 덤프를 해준다는 이야기 같다. 제일 Lowest한 주소부터 그대로 섹션 덤프를 해준다는데... 확인을 해보자.
KKAMAGUI NOTEPAD의 .elf 파일 용량은 500Kbyte이다. 하지만 .arm9 파일 용량은 207Kbyte이다. 사이즈가 상당히 줄었음을 알 수 있다. 그럼 데이터는 어떻게 구성되어있는 걸까?
4.1 objdump로 .elf 분석 및 .arm9 파일 분석
elf 파일의 정보는 devkitPro를 설치하면 있는 objdump.exe 프로그램을 이용하면 볼 수 있다. 이 프로그램과 파일의 정보를 hex로 보여주는 울트라 에디트와 같은 Hex Editor를 사용해서 파일 내용을 비교해 보면 된다. 여기서 Hex Editor는 HxD를 이용하기로 한다. HxD는 http://mh-nexus.de/hxd/ 페이지에서 받을 수 있다.
아래는 HxD로 Notepad.arm9 파일을 연 화면이다.
<HxD의 .arm9 분석 화면>
일단 대충 이런 내용이구나 정보만 알아놓고 objdump 프로그램을 이용해서 Notepad.elf 파일의 섹션 정보를 보자.
<objdump의 실행화면>
LMA와 VMA의 의미에 대해서 잠깐 알아보자.
- LMA(Loaded Memory Address) : 실제 elf 파일이 메모리에 로드되는 위치. VMA와 일반적으로 동일
- VMA(Virtual Memory Address) : 프로그램이 실행될 때 메모리의 위치. ROM 같은 경우는 메모리에 로드된 후에 실행될 위치로 코드가 이동되어야 하므로 일반적으로 다름.
4.2 TCM의 위치 및 메모리 로드 분석
여기서는 ITCM 영역이 링커 스크립트에 의해 여러가지 조건 때문에 제일 앞쪽(0번 섹션)에 위치하지 못하여 어긋난 것 같다. NDS 롬 헤더에 TCM에 대한 정보 필드가 없고, objcopy 프로그램이 단순히 섹션의 내용을 VMA 순서대로 덤프를 하는데... 저 섹션들이 의미가 있을까?
"답은 의미가 있다" 이다.
\devkitPro\devkitARM\arm-eabi\lib 폴더에 가면 ARM9의 CRT 코드를 찾을 수 있다. ds_arm9_crt0.s 파일이 그것인데, 아래와 같은 내용을 담고 있다.
- ldr r1, =__itcm_lma @ Copy instruction tightly coupled memory (itcm section) from LMA to VMA (ROM to RAM)
ldr r2, =__itcm_start
ldr r4, =__itcm_end
bl CopyMemCheck - ldr r1, =__dtcm_lma @ Copy data tightly coupled memory (dtcm section) from LMA to VMA (ROM to RAM)
ldr r2, =__dtcm_start
ldr r4, =__dtcm_end
bl CopyMemCheck
crt0 코드는 링크될때 가장 앞부분에 위치하는 코드로써 홈브루의 초기화 및 기타 작업을 담당하는 것 같다. 위에서 보면 __itcm 같은 변수를 사용하는 것을 알 수 있는데, 이 값들은 링크 스크립트 파일(ds_arm9.ld)을 보면 잘 정의되어있다. 그러므로 binary로 생성될때는 정확한 위치에 있지 않지만, 실행하면서 해당 위치에 옮겨지기때문에 가능하다.
4.3 .elf의 Hex 데이터 분석
그럼 이제 첫번째 섹션 .init의 시작위치를 알았으므로 이제 HxD 프로그램을 이용해서 .elf 파일의 0x8000 영역으로 옮겨보자.
위의 .arm9 파일과 동일함을 알 수 있다.
다른 섹션들도 따라가보면 똑같음을 볼 수 있고, 이로서 objcopy의 -O binary 옵션이 하는 일은 섹션의 VMA 순서에 따라서 첫번째 VMA 주소를 시작으로 메모리 내용을 덤프하는 것과 같음을 알 수 있다.
.arm9파일의 내용은 단순한 코드와 데이터의 집합이다.
5. NDS Tool 분석
이제 마지막 단계만 남았다. .arm9 파일을 이용해서 .nds 파일을 생성하는 단계이다. NDS 파일의 헤더 정보에 대해서는 05 NDS 롬(ROM) 파일 포맷(Format) 분석를 살펴보자. 해당 페이지의 결과를 보면 단순히 헤더를 붙이고 ARM9과 ARM7 코드를 붙여넣는 작업이 전부임을 알 수 있다. ㅜ_ㅜ... 어찌나 간단한지...
참고삼아 ndstool.exe 를 이용하여 분석한 Notepad.nds 파일 정보를 붙여넣었다.
-
D:\ndsl_develop\MyProject\Notepad-업로드용>ndstool -i Notepad.nds
Nintendo DS rom tool 1.33 - Jan 28 2007 21:02:20
by Rafael Vuijk, Dave Murphy, Alexei Karpenko
Header information:
0x00 Game title .
0x0C Game code ####
0x10 Maker code
0x12 Unit code 0x00
0x13 Device type 0x00
0x14 Device capacity 0x01 (2 Mbit)
0x15 reserved 1 000000000000000000
0x1E ROM version 0x00
0x1F reserved 2 0x04
0x20 ARM9 ROM offset 0x200
0x24 ARM9 entry address 0x2000000
0x28 ARM9 RAM address 0x2000000
0x2C ARM9 code size 0x33C74
0x30 ARM7 ROM offset 0x34000
0x34 ARM7 entry address 0x37F8000
0x38 ARM7 RAM address 0x37F8000
0x3C ARM7 code size 0x12B8
0x40 File name table offset 0x35400
0x44 File name table size 0x9
0x48 FAT offset 0x35600
0x4C FAT size 0x0
0x50 ARM9 overlay offset 0x0
0x54 ARM9 overlay size 0x0
0x58 ARM7 overlay offset 0x0339d40x5C ARM7 overlay size 0x0
0x60 ROM control info 1 0x007F7FFF
0x64 ROM control info 2 0x203F1FFF
0x68 Icon/title offset 0x0
0x6C Secure area CRC 0x0000 (-, homebrew)
0x6E ROM control info 3 0x051E
0x70 ARM9 ? 0x0
0x74 ARM7 ? 0x0
0x78 Magic 1 0x00000000
0x7C Magic 2 0x00000000
0x80 Application end offset 0x00035600
0x84 ROM header size 0x00000200
0xA0 ? 0x4D415253
0xA4 ? 0x3131565F
0xA8 ? 0x00000030
0xAC ? 0x53534150
0xB0 ? 0x00963130
0x15C Logo CRC 0x9E1A (OK)
0x15E Header CRC 0xAD78 (OK) -
D:\ndsl_develop\MyProject\Notepad-업로드용>
실제 .arm9 파일의 크기는 0x339D4의 크기인데, NDS Rom 파일이 생성될때 해당 롬의 크기가 0x2A0 만큼 더 큰 것을 보아 약간의 공간이 더 추가된 듯 하다. 그럼 어떤 데이터가 추가된 것을까? 아래는 .arm9 파일과 .nds 파일의 마지막 부분의 내용을 비교한 것이다.
<.arm9 파일(좌측)과 .nds 파일(우측) 내용>
그림이 좀 심하게 일그러졌는데, 첨부파일의 큰 그림을 참조하도록 하고, 여기서 알 수 있는 사실은 추가된 0x2A0 공간만큼 0으로 체워져있다는 사실이다. 따라서 .arm9 바이너리 파일을 그대로 덤프한다는 사실에는 변함이 없다.
마치면서...
NDS 롬(ROM) 파일 생성을 분석하는데, 자그마치 이틀이나 걸렸다. 상당히 복잡했고 이것 저것 볼 것도 많았지만, 확실히 NDS 파일에 대해서 알 수 있는 좋은 기회였다. 결국 NDS 파일은 ARM9과 ARM7 코드와 데이터를 포함하는 간단한 구조였던 것이다.
이제 NDS는 나의 장난감이 된 것이나 다름없다. >ㅁ<)/~ 기다려라~ NDS야. 하하하하핫~~!!!
이 글은 스프링노트에서 작성되었습니다.
'NDS 홈브루(Homebrew) > 홈브루 Tutorial' 카테고리의 다른 글
01 libfat 업그레이드 (2) | 2007.11.14 |
---|---|
00 NDS 홈브루 프로젝트 생성 (8) | 2007.11.14 |
00 NDS 개발 킷(Devkit Pro) 설치 (0) | 2007.11.14 |
참고. NDS 동영상 인코딩(BatchDpg) (2) | 2007.11.14 |
참고. 3 in 1 Expansion Pack 사용법 (0) | 2007.11.14 |