Troubleshooting

[임베디드 빌드] 링커 스크립트(.ld) 메모리 초과 에러(Flash/RAM Overflow) 해결 가이드

임베디드 친구 2026. 6. 12. 20:57
반응형

[Quick Summary - For Global Developers]

  • Symptom: 펌웨어 빌드(Build) 단계에서 GNU 툴체인이 컴파일을 중단하고 region FLASH' overflowed by X bytes 또는 region RAM' overflowed 형태의 치명적인 링커 에러(Linker Error)를 발생시킴.
  • Cause: 임베디드 소스 코드 내 전역 변수 크기, 상수 데이터(RO data) 및 컴파일된 바이너리 코드(.text)의 총용량이 링커 스크립트(.ld)에 정의된 물리 메모리 경계 레지스터 영역(Memory Region) 크기를 초과함.
  • Solution: .ld 파일의 MEMORY 섹션LENGTH 매개변수를 데이터시트에 맞게 교차 검증하거나, 큰 배열을 Flash 영역으로 이동(const 선언) 및 툴체인의 컴파일러 최적화 옵션(-Os)을 활성화하여 바이너리 풋프린트를 압축함.

링커 스크립트 메모리 오버플로우(Linker Script Memory Overflow) 발생 증상

임베디드 프로젝트의 규모가 커지거나 외부 오픈소스 라이브러리를 포팅하는 과정에서 툴체인 빌드 중 다음과 같은 링크 타임 에러 메시지를 마주하는 경우가 있습니다.

arm-none-eabi-ld: bsp.elf section `.text' will not fit in region `FLASH'
arm-none-eabi-ld: region `FLASH' overflowed by 24560 bytes
arm-none-eabi-ld: region `RAM' overflowed by 1024 bytes collect2: error: ld returned 1 exit status

이 오류는 컴파일러(Compiler)가 개별 소스 코드(.c)를 오브젝트 파일(.o)로 변환하는 데는 성공했으나, GNU 링커(ld)가 최종 바이너리(.elf, .bin)를 생성하기 위해 파일들을 병합하는 과정에서 발생합니다. 타겟 MCU가 제공하는 물리적 하드웨어 사양을 초과하는 코드나 데이터가 배치되어 주소 공간을 더이상 할당할 수 없는 경우 빌드가 중단되는 증상입니다.

메모리 초과 에러(Flash/RAM Overflow)의 근본적인 원인 분석

링커 스크립트 메모리 초과 에러가 발생하는 원인은 링커 스크립트(.ld) 내부의 MEMORY 선언문 규격과 컴파일러가 생성한 섹션(Section) 배열이 일치하지 않기 때문입니다.

1. 링커 스크립트의 MEMORY 섹션 아키텍처

GNU LD 링커 스크립트는 하드웨어 데이터시트(Datasheet)의 메모리 맵(Memory Map)을 기반으로 물리 주소의 시작점(ORIGIN)과 총 용량(LENGTH)을 관리합니다.

  • FLASH (RX): 실행 코드(.text)와 읽기 전용 데이터(.rodata)가 배치되는 비휘발성 영역.
  • RAM (WRA): 초기화된 전역 변수(.data), 초기화되지 않은 전역 변수(.bss), 그리고 런타임 스택(Stack)과 힙(Heap)이 배치되는 휘발성 영역.

2. 컴파일러 최적화 배제와 섹션 비대화

C 코드가 기계어로 번역될 때, 최적화 옵션(-O0)이 적용되면 인라인화(Inlining)가 억제되고 데드 코드(Dead Code)가 그대로 유지되어 .text 섹션의 크기가 급격히 늘어납니다. 또한 대용량 고정 룩업 테이블(Lookup Table)이나 이미지 버퍼 배열을 선언할 때 명시적으로 속성을 지정하지 않으면, RAM 영역을 과도하게 할당받아 오버플로우가 발생하게 됩니다.

RAM 오버플로우를 유발하는 잘못된 C 코드 예시 (Bad Case)

개발자가 대용량 상수 데이터를 다룰 때 const 한정자를 누락하거나 전역 스택 공간을 비효율적으로 설계하면, 아래 코드와 같이 RAM의 한계를 초과하는 치명적인 버그가 발생합니다.

#include <stdint.h>

/* BAD CASE: Missing 'const' qualifier for raw image display data */
/* This look-up table is allocated to RAM (.data section) instead of FLASH (.rodata section) */
uint8_t g_display_frame_buffer[65536] = {
    0x42, 0x4D, 0x3E, 0x00, 0x03, 0x00, 0x00, 0x00,
    /* 64KB of dense binary raw pixels here */
};

/* Uninitialized large global array mapped to RAM (.bss section) */
uint32_t g_sensor_log_matrix[16384]; 

void System_Process(void) {
    /* Local variables occupying runtime stack */
    uint8_t local_process_buffer[4096];

    for(uint32_t i = 0; i < 65536; i++) {
        /* Missing bound checking or heavy mathematical operation */
        g_display_frame_buffer[i] ^= 0xFF;
    }
}

Flash/RAM Overflow 해결을 위한 올바른 링커 스크립트 설정 및 C 코드 (Good Case)

메모리 공간을 효율적으로 재배치하고 링커가 정상적으로 최종 주소를 바인딩할 수 있도록 개선한 코드 및 스크립트 예시입니다.

1. 수정된 링커 스크립트 (Linker Script - stm32f4xx.ld)

/* GOOD CASE: Verifying and setting correct hardware memory boundaries */
MEMORY
{
  CCMRAM    (xrw)    : ORIGIN = 0x10000000, LENGTH = 64K
  RAM       (xrw)    : ORIGIN = 0x20000000, LENGTH = 128K  /* Retained RAM boundaries */
  FLASH     (rx)     : ORIGIN = 0x08000000, LENGTH = 512K  /* Confirmed target flash size */
}

SECTIONS
{
  /* User-defined block to forcefully redirect specific variables to CCMRAM */
  .ccmram_section :
  {
    . = ALIGN(4);
    *(.ccmram)
    *(.ccmram*)
    . = ALIGN(4);
  } > CCMRAM
}

2. 방어적으로 구현된 C 소스 코드

#include <stdint.h>

/* GOOD CASE: Adding 'const' strictly routes this large array to FLASH (.rodata) */
const uint8_t g_display_frame_buffer[65536] = {
    0x42, 0x4D, 0x3E, 0x00, 0x03, 0x00, 0x00, 0x00,
    /* Stored in non-volatile storage safely, saving 64KB RAM */
};

/* Relocating large uninitialized runtime array to CCMRAM region using section attribute */
__attribute__((section(".ccmram"))) uint32_t g_sensor_log_matrix[16384];

void System_Process(void) {
    /* Stack optimization: Allocate dynamically or keep minimal sized footprint */
    static uint8_t s_local_process_buffer[256]; 

    /* Processing code using low-footprint registers */
}

핵심 수정 포인트 설명

  • const 한정자 명시를 통한 .rodata 이행: 대용량 배열 앞에 const를 선언하여 링커가 이 데이터를 RAM(.data)이 아닌 읽기 전용 FLASH(.rodata) 영역에 배치하도록 격리합니다.
  • attribute((section(".name"))) 활용: 기본 RAM 공간이 부족할 경우, MCU 고유의 특수 메모리 영역(예: CCMRAM, SRAM2)으로 특정 전역 변수를 강제로 이동 배치해서 일반 RAM의 병목 현상을 해소합니다.
  • 지역 변수 스택 풋프린트 최소화: 함수 내부에서 대용량 버퍼를 선언하는 대신 static 키워드를 활용해 힙/스택 오버플로우 위험을 방지합니다.

링커 파일 디버깅 및 트러블슈팅 가이드

오버플로우 버그 발생 시 신속하게 메모리 점유율을 추적하고 최적화하기 위한 실무 노하우입니다.

  • 컴파일러 맵 파일(Map File) 분석: 툴체인 빌드 옵션에 -Wl,-Map=output.map 플래그를 추가하고, 생성된 .map 텍스트 파일의 Linker 섹션을 검색하면 어떤 파일의 어떤 함수/전역 변수가 물리 메모리를 가장 많이 소모하고 있는지 바이트(Byte) 단위로 확인이 가능합니다.
  • 크기 분석 유틸리티(size) 활용: GNU 툴체인의 arm-none-eabi-size 명령어를 빌드 후킹 스크립트에 등록하고, 최종 출력 파일(.elf)의 text, data, bss 점유량을 모니터링하여 병목을 차단할 수 있습니다.
  • 가비지 컬렉션(Garbage Collection) 옵션 적용: 사용되지 않는 데드 코드를 자동으로 제거하기 위해 컴파일러 단계에 -ffunction-sections -fdata-sections를 명시하고, 링커 단계에 -Wl,--gc-sections 옵션을 활성화하여 최종 바이너리 크기를 압축하십시오.
반응형