임베디드 면접 학습 노트 목차

펌웨어 빌드·부팅 — 임베디드 면접 (실제 기출 기반·심화)

".data와 .bss 차이", "전역변수는 누가·언제 초기화하나", "전원 켜고 main()까지 부팅 과정", "벡터 테이블", "링커 스크립트가 뭐냐", "크로스 컴파일이 뭐냐", "OTA 중 전원 꺼지면" — 컴파일러·메모리·부팅을 이해했는지 보는 단골. 메모리 섹션·부팅 과정·volatile은 신입·경력 불문 거의 빠지지 않는다.

함정 다발(★): ① 전역변수는 컴파일러가 자동 초기화? → 아님·startup 코드(C 런타임)가 main 직전에.bss가 ROM을 차지? → 아님·SHT_NOBITS(0바이트)·startup이 RAM 0-fill ③ main()이 첫 진입점? → Reset_Handler가 먼저 ④ const는 RAM? → 보통 .rodata=ROM펌웨어=.elf를 그대로 굽는다? → objcopy로 .bin/.hex 변환.


1. 빌드 과정 — 전처리→컴파일→어셈블→링크→objcopy (★ 단골)

실제 질문: "소스가 실행파일(HEX/BIN)이 되기까지 과정을 설명하라" · "ELF를 그대로 안 굽고 .bin/.hex로 바꾸는 이유?" · "컴파일과 빌드 차이?"

단계도구/플래그산출물
전처리gcc -E(cpp)매크로·#include 전개된 소스
컴파일gcc -S.s 어셈블리
어셈블gcc -c(as).o 오브젝트(기계어)
링크ld.elf(심볼·디버그·여러 섹션)
변환objcopy -O binary/ihex.bin / .hex
  • 링크는 여러 .o심볼 해석(symbol resolution) + 재배치(relocation) + 라이브러리 결합.
  • objcopy 변환이 필요한 이유(★): ELF엔 심볼·디버그·섹션헤더 메타데이터가 붙고 서로 다른 주소의 세그먼트가 섞임. 플래시 도구는 순수 .bin(주소 없음·시작주소 별도) 또는 .hex(주소+체크섬 포함 텍스트)를 원함 → arm-none-eabi-objcopy -O binary main.elf main.bin.

꼬리질문: ".bin vs .hex?" → .bin은 주소 없는 순수 바이너리·.hex(Intel HEX)는 주소+체크섬. / "정적 vs 동적 링크?" → 임베디드는 보통 정적(OS·동적 로더 없음).

함정: ❌ "펌웨어=.elf를 그대로 굽는다" → objcopy로 .bin/.hex 변환. ❌ ".bin과 .hex가 같다" → 주소 정보 유무가 다름.


2. 메모리 섹션 — .text/.rodata/.data/.bss/heap/stack (★★★ 최다 출제)

실제 질문: ".data와 .bss의 차이는?"(최빈출) · "메모리 영역(code/data/heap/stack)을 설명하라" · "전역/static 변수는 어디 저장?" · "초기화된 전역변수는 ROM이냐 RAM이냐?" · "BSS 약자는?"

섹션내용위치
.text코드·실행 명령ROM/Flash
.rodataconst·문자열 리터럴·룩업테이블ROM(읽기전용)
.data초기값 있는 전역/staticROM(LMA)에 초기값 + RAM(VMA)에 배치·startup이 복사
.bss0/미초기화 전역/staticROM 0바이트(SHT_NOBITS)·startup이 RAM 0-fill
heapmallocRAM(낮은→높은 주소)
stack지역변수·복귀주소RAM(보통 높은→낮은·LIFO)
  • .data vs .bss 핵심(★): 초기값을 주면 .data(int a=5;안 주거나 0이면 .bss(int b;). BSS = Block Started by Symbol.
  • 왜 .data는 ROM+RAM 둘 다?: RAM은 휘발성이라 초기값은 *비휘발 ROM(LMA)*에 저장하고, 실행 중 쓰기 위해 *RAM(VMA)*으로 복사. → "초기화된 전역변수는?" 정답은 둘 다.
  • .bss가 ROM 0바이트인 이유: 어차피 0이라 저장이 무의미 → SHT_NOBITS(파일 공간 0)·startup이 런타임 RAM에서 0으로 채움 → 바이너리 크기 절감.
  • 실증(면접 단골): int a[100000]={1,}; → 실행파일 ~400kB(.data) vs int a[100000]; → 파일 거의 0(.bss).
  • stack vs heap: stack=컴파일타임 프레임·자동 소멸·LIFO / heap=런타임·수동 관리(malloc/free)·단편화. 임베디드는 RAM 양끝에서 마주보고 성장. (malloc은 단편화·예측불가로 임베디드에서 기피)

꼬리질문: "const는 어디?" → 보통 .rodata=ROM(포인터로 강제수정 시 HardFault). / "스택 오버플로가 .bss/heap 침범?" → MPU 없으면 silent corruption → 링커 배치·MPU 가드. / "volatile은 왜?"(★ must-know) → HW 레지스터·ISR 공유변수에서 컴파일러 최적화(레지스터 캐싱) 방지.

함정: ❌ ".bss가 ROM을 차지" → SHT_NOBITS·0바이트. ❌ ".bss를 startup이 ROM→RAM 복사" → 복사 아니라 0-fill(복사는 .data만). ❌ "문자열 리터럴은 stack/heap" → .rodata.


3. 링커 스크립트 (.ld) — VMA vs LMA (★★ 중상)

실제 질문: "링커 스크립트가 뭐고 왜 필요?" · "VMA와 LMA 차이?" · ".bss는 왜 RAM에만, 왜 나누나?"(ROM 절약)

  • VMA(Virtual Memory Address) = 실행 시 주소 · LMA(Load Memory Address) = 저장/로드 위치. 대부분 같지만 .data는 다르다(LMA=ROM·VMA=RAM) → startup 복사의 근본 이유. GNU ld: "data section is loaded into ROM, and copied into RAM at startup".
  • MEMORY: 영역 정의 — ROM (rx): ORIGIN=0x8000000, LENGTH=512K / RAM (xrw): ORIGIN=0x20000000, LENGTH=128K.
  • SECTIONS: 입력 섹션을 출력 섹션·주소에 배치. > region=VMA·AT> region=LMA.
_sidata = LOADADDR(.data);
.data : { _sdata = .; *(.data*) _edata = .; } >RAM AT> ROM   /* VMA=RAM, LMA=ROM */
.bss (NOLOAD) : { _sbss = .; *(.bss*) *(COMMON) _ebss = .; } >RAM   /* Flash 미점유 */
  • 링커 심볼은 값이 아니라 주소: _sidata(.data의 LMA)·_sdata/_edata(.data VMA)·_sbss/_ebss·_estack(RAM 끝·초기 SP). C에선 &_sdata로 접근.
  • 베어메탈 필수 이유: PC용 ld는 default script 내장이라 없어도 빌드되지만, 베어메탈은 OS·기본 메모리맵이 없고 리셋 시 CPU가 flash origin에서 벡터테이블을 읽으므로 .isr_vector를 맨 앞에 강제 배치 → 필수.

꼬리질문: "왜 .data만 AT> 쓰나?" → 초기값 저장(ROM)과 실행(RAM) 위치가 달라서. / "_sidata가 뭐냐?" → .data의 LMA(si=start of init data). / "startup 복사 의사코드?" → for(d=&_sdata, s=&_sidata; d<&_edata;) *d++=*s++;.

함정: ❌ "VMA=LMA 항상 같다" → .data는 다름. ❌ ".data>ROM AT> RAM" → 반대·정답 >RAM AT> ROM(앞=VMA 실행=RAM). ❌ "_etext는 값" → 주소.


4. startup·부팅 — reset→main (★★★ 개념 단골·순서 정확히)

실제 질문: "전원 켜고 main()까지 부팅 과정?" · "전역변수는 누가·언제 초기화?" · "벡터 테이블이 뭐고 어디?" · "Cortex-M 리셋 후 가장 먼저?"

부팅 순서(★ ST CMSIS GCC startup 실코드로 검증):

다이어그램 로딩 중…
  • [HW] 리셋 직후(Cortex-M 고유): 0번지 명령어를 실행하지 않고 벡터테이블 첫 두 워드를 데이터로 읽음0x0000_0000 → MSP(초기 스택), 0x0000_0004 → PC(Reset_Handler). 고전 ARM7과 다름. → "벡터테이블 0번지에 왜 SP?"의 답: 첫 C 코드 실행 전에 스택을 확보해야 하므로 HW가 자동 세팅.
  • [SW] Reset_Handler: SP 설정 → SystemInit(클럭/PLL/FPU·VTOR) → .data 복사(_sidata_sdata.._edata) → .bss 0클리어(_sbss.._ebss) → __libc_init_array(C++ 전역 생성자·constructor 속성) → main().
  • 순서 주의(★ 심화): ST 표준은 SystemInit이 .data/.bss 초기화보다 먼저(외부 SDRAM에 .data/.bss가 있을 수 있어 클럭/FMC를 먼저 켜야) → SystemInit 안에선 전역변수 의존 금지. (toolchain마다 순서가 다를 수 있음 — 이 트레이드오프 인지가 포인트.)
  • 누가 전역변수를 초기화?(★): 컴파일러도 OS도 아닌 startup 코드(C 런타임/crt0)가 main 직전에 .data 복사 + .bss 0-fill. 컴파일러/링커는 초기값을 Flash에 배치하고 심볼 주소만 정함.

꼬리질문: ".bss 0-fill 안 하면?" → static 변수가 RAM 잔류 쓰레기값(C 표준의 0 보장은 startup이 구현). / "main 직행하면?" → SP 미설정·전역 쓰레기값 → 즉시 크래시. / "VTOR 재설정?" → 부트로더→앱 점프 시 앱 벡터테이블로 바꿔야 인터럽트가 앱 핸들러로.

함정: ❌ "컴파일러가 전역변수 자동 초기화" → startup이. ❌ "main이 첫 진입점" → Reset_Handler 먼저. ❌ "리셋 시 0번지 명령어 실행" → 데이터로 읽어 SP에. ❌ "벡터테이블 0번지=reset handler" → 0번지=SP·4번지=reset.


5. 크로스 컴파일·부트로더·OTA (★★ 단골)

실제 질문: "크로스 컴파일이 뭐냐? 왜 필요?" · "부트로더 역할?" · "OTA 어떻게 동작? 중간에 전원 꺼지면?"

  • 크로스 컴파일: 호스트(컴파일러 실행)≠타깃(코드 실행). 호스트 x86 gcc는 x86 명령어를 내므로 ARM에서 실행 불가 → arm-none-eabi-gcc 필요. triplet arm-none-eabi = arm 아키텍처·none(vendor·OS 없음=베어메탈)·eabi. 대비 arm-linux-gnueabihf=Linux 유저스페이스(glibc).
  • newlib-nano(--specs=nano.specs): 작은 Flash/RAM·printf float 기본 비활성. map 파일(-Wl,-Map=out.map): 섹션 크기·심볼 주소·FLASH/RAM 사용량 → 크기 초과 디버깅.
  • 부트로더→앱 점프(Cortex-M): ① 앱 벡터테이블 첫 워드로 MSP 설정SCB->VTOR = APP_ADDR로 VTOR 재설정 ③ 둘째 워드(Reset_Handler)로 점프. VTOR 안 바꾸면 인터럽트 시 부트로더 핸들러로 → HardFault.
  • OTA·A/B(이중 뱅크): 비활성 슬롯에 업데이트 기록 → 검증 후 다음 부팅에 전환 → 실패 시 이전 슬롯 롤백. 전원 끊겨도 기존 이미지 무손상 = 벽돌 방지(MCUboot·ESP-IDF). 무결성(CRC/해시)진위(서명 RSA/ECDSA=Secure Boot) — 둘 다 필요.

꼬리질문: "OTA 중 전원 끊기면?" → 비활성 슬롯만 기록 중이라 활성 무손상·재시도/롤백(단일뱅크 덮어쓰기만 위험). / "A/B 트레이드오프?" → Flash 2배지만 안전·무중단·롤백. / "CRC만으로 보안?" → CRC는 무결성만·변조 방지는 서명.

함정: ❌ "호스트 gcc로 임베디드 빌드" → x86 명령어라 불가. ❌ "OTA 중 전원=무조건 벽돌" → A/B면 복구. ❌ "VTOR 재설정 없이 점프" → 인터럽트 시 HardFault.


한국 임베디드 면접 단골 Q&A (답변 골격)

질문핵심 답
빌드 과정전처리→컴파일→어셈블→링크(.elf)→objcopy(.bin/.hex)
.elf를 왜 변환메타데이터 제거·플래시 도구는 .bin/.hex
.data vs .bss초기값 有(ROM+RAM) vs 無/0(ROM 0바이트·RAM 0-fill)
전역변수 초기화startup 코드(C런타임)가 main 직전에
초기화 전역변수 위치ROM(초기값)+RAM(실행) 둘 다
const 위치.rodata=ROM
VMA vs LMA실행 주소 vs 로드 주소(.data만 다름)
링커 스크립트 왜메모리맵·벡터테이블 배치(베어메탈 필수)
부팅 순서HW(MSP·PC)→Reset_Handler→SystemInit→.data복사→.bss클리어→libc_init→main
벡터테이블 0번지초기 SP·4번지=Reset_Handler
크로스 컴파일호스트≠타깃·arm-none-eabi(none=베어메탈)
OTA 안전A/B 슬롯·롤백·서명

꼬리질문 대비 (상 난이도)

  • "왜 .data는 ROM·RAM 둘 다 필요?" → 초기값은 비휘발 ROM·실행은 쓰기가능 RAM.
  • ".bss를 startup이 0으로 안 채우면?" → static 변수 쓰레기값·C 표준 위반 상태.
  • "SystemInit이 .data 복사보다 먼저인 이유?" → 외부 SDRAM에 .data/.bss가 있을 수 있어 클럭/FMC 먼저(전역변수 의존 금지).
  • "스택 오버플로가 .bss/heap 침범?" → MPU 없으면 silent corruption → 링커 배치·MPU 가드.
  • "__libc_init_array 안 부르면 C++에서?" → 전역 객체 생성자 미호출.
  • "VTOR 재설정 안 하면?" → 인터럽트가 부트로더 핸들러로 → HardFault.

한 줄 요약 — 빌드 = 전처리→컴파일→어셈블→링크(.elf)→objcopy(.bin/.hex). 메모리 섹션 — .text/.rodata(ROM)·.data(초기값 有·ROM+RAM.bss(0·ROM 0바이트 SHT_NOBITS·RAM 0-fill)·stack/heap. 링커 스크립트는 **VMA(실행)≠LMA(로드)**를 분리(.data는 >RAM AT> ROM)·베어메탈 필수. 부팅 = [HW] 0번지→MSP·4번지→PC → Reset_Handler → SystemInit → .data복사 → .bss클리어 → __libc_init_array → main·전역변수 초기화 주체는 컴파일러도 OS도 아닌 startup 코드. 크로스 컴파일(arm-none-eabi·none=베어메탈)·OTA는 A/B 슬롯+롤백+서명.

(출처 — 한국 면접 기출·1차자료 교차검증 2026-06: velog @embeddedjune(부팅·컴파일)·@ebing·@ws_jung·KLDP(.data/.bss 실증)·80000coding(BSS 약자) · 1차자료: System V gABI — ELF Sections(SHT_NOBITS)·GNU ld manual — VMA/LMA·SECTIONS·GCC Overall Options·ST startup_stm32f407xx.s·ARM Cortex-M4 Generic User Guide — Vector table·Memfault — zero-to-main·MCUboot design 교차검증.)

주변장치·타이머 — GPIO(push-pull/open-drain)·PWM(PSC/ARR/CCR)·워치독·ADC·클럭임베디드 C++·코드 품질 — vtable·예외/RTTI·동적할당·MISRA·volatile