Function Pointer, callback, hooking

Seungyun Lee·2026년 2월 9일

RTOS

목록 보기
4/14

1. 함수 포인터 (Function Pointer)의 기초

우리가 흔히 쓰는 변수 포인터(int *ptr)는 데이터가 저장된 주소를 가리킵니다. 마찬가지로 함수 포인터는 함수 코드가 저장된 메모리 주소를 가리키는 변수입니다. 함수 포인터를 사용하면 함수를 변수처럼 주고받거나, 실행 중에 어떤 함수를 호출할지 결정할 수 있습니다.

선언 및 사용법

1. 선언 (Declaration):

void (*TaskPt)(void);

void: 이 함수 포인터가 가리키는 함수는 리턴값이 없습니다.
(*TaskPt): TaskPt라는 이름의 포인터 변수입니다.
(void): 이 함수 포인터가 가리키는 함수는 입력 매개변수(인자)가 없습니다.
해석: "TaskPt는 입력도 없고 출력도 없는 함수를 가리키는 포인터다."

2. 할당 (Assignment):

TaskPt = &CallMe; // TaskPt points to CallMe

CallMe라는 함수의 주소를 TaskPt에 저장합니다. 이제 TaskPt는 CallMe 함수를 가리킵니다. 함수 이름 자체(CallMe)가 주소를 의미하므로 &를 생략하고 TaskPt = CallMe;라고 써도 됩니다.

3. 호출 (Dereferencing/Call):

(*TaskPt)(); // call the function to which it points

TaskPt가 가리키는 함수(CallMe)를 실행합니다. *를 통해 포인터를 역참조하여 함수를 찾아가고 ()를 통해 실행합니다. 단순히 TaskPt();라고 써도 실행됩니다.

2. 콜백 함수 (Callback Function)

콜백은 "나중에 다시 불러줘(Call me back)"라는 개념입니다. 내가 함수 A를 호출하면서 "이 작업이 끝나거나 특정 이벤트가 발생하면, 내가 넘겨준 함수 B를 실행해 줘"라고 부탁하는 방식입니다. 이때 넘겨주는 함수 B가 바로 콜백 함수입니다.

작동원리

모듈 A (User Code): 사용자가 작성한 코드입니다.
모듈 B (OS): 운영체제나 라이브러리입니다.
과정:
사용자는 CallMe라는 함수를 먼저 작성합니다.

int count;
void CallMe(void){
    count++;
}
  • 사용자는 OS의 함수를 호출할 때, CallMe 함수의 주소(함수 포인터)를 매개변수(Parameter)로 넘겨줍니다.
  • OS는 즉시 사용자에게 제어권을 돌려주지만(Return), 나중에 "약속된 조건(agreed upon condition)"이 만족되면 아까 받은 주소를 이용해 CallMe 함수를 실행합니다.

일상생활 예시
상황: 식당에 가서 대기 명단에 이름을 적습니다.
일반 호출: 줄 서서 계속 기다리다가 내 차례가 되면 들어갑니다. (Blocking)
콜백: 전화번호(함수 포인터)를 남기고 쇼핑하러 갑니다. 자리가 나면(조건 충족) 직원이 전화를 겁니다(콜백 실행).

3. 후킹 (Hooking)

후킹은 함수 포인터를 활용한 또 다른 강력한 기법입니다. 운영체제나 프로그램의 중간 과정에 사용자가 만든 함수를 "끼워 넣는(Hook)" 것을 말합니다. 낚싯바늘(Hook)로 중간에 무언가를 낚아채는 것과 비슷합니다.

후킹의 용도
운영체제(OS)는 중요한 지점마다 사용자가 원하는 코드를 실행할 수 있도록 "빈자리(Hook point)"를 만들어 둡니다.

전략적 위치 (Strategic Places):

  • OS 초기화가 끝났을 때
  • 스케줄러가 실행될 때 (매번 작업이 바뀔 때마다)
  • 새로운 스레드가 생성될 때

사용 방법:
1. 사용자가 함수를 작성합니다 (예: MyDebugFunction).
2. OS의 등록 함수(예: OS_AddHook)를 호출하면서 내 함수의 주소를 넘깁니다.
3. 해당 이벤트가 발생할 때마다 OS는 MyDebugFunction을 실행합니다.

장점: 디버깅할 때 매우 유용합니다. 예를 들어, 스케줄러가 실행될 때마다 로그를 남기는 함수를 끼워 넣으면 시스템의 동작을 추적하기 쉽습니다.

4. 종합 정리 및 예시 코드

#include <stdio.h>

// 1. 함수 포인터 타입 정의 (입력 X, 리턴 X)
typedef void (*TaskPointer)(void);

// 전역 변수로 훅(Hook)을 저장할 변수 선언 (초기엔 아무것도 없음)
TaskPointer ButtonPressHook = NULL;

// --- [OS / 라이브러리 영역] ---
// 버튼이 눌렸을 때 시스템 내부에서 호출되는 함수
void OS_HandleButtonPress() {
    printf("[OS] 하드웨어 버튼 감지됨!\n");

    // 만약 사용자가 등록한 훅이 있다면 실행 (이게 바로 후킹!)
    if (ButtonPressHook != NULL) {
        printf("[OS] 사용자 훅 실행...\n");
        ButtonPressHook(); // 사용자의 함수를 대신 실행해줌 (콜백)
    }
}

// 사용자가 훅을 등록할 수 있게 해주는 함수
void OS_SetButtonHook(TaskPointer userFunction) {
    ButtonPressHook = userFunction;
}

// --- [사용자 영역] ---
// 사용자가 만든 콜백 함수
void MyLedOnFunction() {
    printf(">> [User] LED를 켭니다!\n");
}

int main() {
    // 1. 훅 등록 전: 버튼 눌러도 OS 메시지만 나옴
    printf("--- 훅 등록 전 ---\n");
    OS_HandleButtonPress();

    // 2. 훅 등록 (함수 포인터 전달)
    OS_SetButtonHook(&MyLedOnFunction);

    // 3. 훅 등록 후: 버튼 누르면 내 함수도 같이 실행됨
    printf("\n--- 훅 등록 후 ---\n");
    OS_HandleButtonPress();

    return 0;
}

실행결과

--- 훅 등록 전 ---
[OS] 하드웨어 버튼 감지됨!

--- 훅 등록 후 ---
[OS] 하드웨어 버튼 감지됨!
[OS] 사용자 훅 실행...
>> [User] LED를 켭니다!
  • TaskPointer는 함수 포인터 타입입니다.
  • MyLedOnFunction은 콜백 함수입니다. (OS에 의해 나중에 호출되므로)
  • OS_SetButtonHook을 통해 내 함수를 등록하는 과정이 후킹(Hooking)입니다. OS의 버튼 처리 흐름 중간에 내 기능을 끼워 넣었기 때문입니다.
profile
RTL, FPGA Engineer

0개의 댓글