일반적으로 "C 프로그램은 main() 함수에서 시작한다"고 알고 있지만, 실제로는 그 이전에 특별한 start-up 루틴이 존재합니다.
커널 → _start → __libc_start_main → main()
실행할 때마다 메모리 주소가 바뀜:
- Stack 시작 주소
- Heap 시작 주소
- 라이브러리 로드 주소
→ 공격자가 특정 주소를 예측할 수 없게 만듦
int main(int argc, char *argv[]);
| 매개변수 | 타입 | 의미 |
|---|---|---|
argc | int | 명령줄 인수의 개수 |
argv | char*[] | 명령줄 인수 문자열 배열 |
argv[0]은 항상 프로그램 이름 (실행 경로)argv[argc]는 항상 NULL#include <stdio.h>
int main(int argc, char *argv[]) {
printf("Number of Arguments Passed: %d\n", argc);
// 모든 명령줄 인수 출력
for(int i = 0; i < argc; i++) {
printf("argv[%d]: %s\n", i, argv[i]);
}
return 0;
}
$ gcc cli_args.c -o cli_args
$ ./cli_args
Number of Arguments Passed: 1
argv[0]: ./cli_args
$ ./cli_args hello world good morning
Number of Arguments Passed: 5
argv[0]: ./cli_args
argv[1]: hello
argv[2]: world
argv[3]: good
argv[4]: morning
리눅스 운영체제는 character array 포인터에 저장된 환경 변수 목록을 관리합니다.
형식: name=value
예시: HOME=/home/pi
PATH=/usr/bin:/bin
LANG=en_US.UTF-8
extern char **environ;
#include <stdio.h>
int main() {
extern char **environ;
char **env = environ;
// 모든 환경 변수 출력
while(*env != NULL) {
printf("%s\n", *env);
env++;
}
return 0;
}
$ gcc env_list.c -o env_list
$ ./env_list
SHELL=/bin/bash
PWD=/home/pi/project
LOGNAME=pi
HOME=/home/pi
LANG=C.UTF-8
PATH=/usr/local/bin:/usr/bin:/bin
...
char *getenv(const char *name);
int setenv(const char *name, const char *value, int overwrite);
int unsetenv(const char *name);
| 함수 | 기능 | 반환값 |
|---|---|---|
getenv() | 환경 변수 값 가져오기 | 값 문자열 또는 NULL |
setenv() | 환경 변수 설정/수정 | 성공 0, 실패 -1 |
unsetenv() | 환경 변수 삭제 | 성공 0, 실패 -1 |
#include <stdio.h>
#include <stdlib.h>
int main() {
char env_name[15];
char env_value[255];
printf("Enter Variable name: ");
scanf("%s", env_name);
printf("Enter Variable value: ");
scanf("%s", env_value);
// 환경 변수 설정
int status = setenv(env_name, env_value, 1);
if(status == 0) {
printf("Environment variable created successfully!\n");
} else {
printf("Environment variable creation failed!\n");
}
// 값 확인
printf("%s=%s\n", env_name, getenv(env_name));
// 삭제
unsetenv(env_name);
printf("After delete: %s=%s\n", env_name, getenv(env_name));
return 0;
}
$ gcc env_set_get_del.c -o env_test
$ ./env_test
Enter Variable name: MY_VAR
Enter Variable value: hello
Environment variable created successfully!
MY_VAR=hello
After delete: MY_VAR=(null)
$ echo $MY_VAR
# 빈 값 (프로세스 내부 변경은 Shell에 영향 없음)
자식 프로세스는 부모의 환경 변수를 물려받지만,
자식이 수정한 내용은 부모에게 영향을 주지 않음
Shell (부모)
└─ export MY_DATA="Parent_Value"
└─ ./program (자식)
└─ setenv("MY_DATA", "Child_Value") ← 자식만 변경
Shell에서 확인 → 여전히 "Parent_Value"
#include <stdio.h>
#include <stdlib.h>
int main() {
char *name = "MY_DATA";
char *val;
// 1. 부모로부터 상속받은 값 확인
val = getenv(name);
if (val == NULL) {
printf("[Child] '%s' 환경 변수가 없습니다.\n", name);
} else {
printf("[Child] 상속받은 값: %s\n", val);
}
// 2. 자식 프로세스 내에서 값 변경
printf("[Child] 값을 'Child_Value'로 변경...\n");
setenv(name, "Child_Value", 1);
// 3. 변경된 값 확인
printf("[Child] 변경된 값: %s\n", getenv(name));
printf("[Child] 프로세스 종료.\n");
return 0;
}
# 1. 컴파일
$ gcc env_inherit.c -o env_inherit
# 2. 부모(Shell)에서 환경 변수 설정
$ export MY_DATA="Parent_Value"
$ echo $MY_DATA
Parent_Value
# 3. 자식 프로세스 실행
$ ./env_inherit
[Child] 상속받은 값: Parent_Value
[Child] 값을 'Child_Value'로 변경...
[Child] 변경된 값: Child_Value
[Child] 프로세스 종료.
# 4. 부모(Shell) 값 재확인 → 변화 없음!
$ echo $MY_DATA
Parent_Value
리눅스 프로세스는 가상 메모리 공간을 사용하며, ELF 포맷에 따라 구획됩니다.
높은 주소
┌─────────────────┐
│ Command Line │ ← 명령줄 인수, 환경변수
│ & Environment │
├─────────────────┤
│ Stack │ ← 지역변수, 함수 호출
│ ↓ │ (높은 주소 → 낮은 주소)
├─────────────────┤
│ │
│ (Free Space) │
│ │
├─────────────────┤
│ ↑ │
│ Heap │ ← 동적 할당 (malloc)
│ │ (낮은 주소 → 높은 주소)
├─────────────────┤
│ BSS Segment │ ← 초기화 안 된 전역변수
│ │ (자동으로 0 초기화)
├─────────────────┤
│ Data Segment │ ← 초기화된 전역변수
│ (Initialized) │
├─────────────────┤
│ Text Segment │ ← 실행 코드 + 문자열 리터럴
│ (Code) │ (Read-Only)
└─────────────────┘
낮은 주소
| 영역 | 내용 | 특징 |
|---|---|---|
| Text (Code) | 실행 코드, 문자열 리터럴 | Read-Only |
| Data | 초기화된 전역변수 int a=10; | Read-Write |
| BSS | 초기화 안 된 전역변수 int b; | 자동 0 초기화 |
| Heap | 동적 할당 (malloc) | 위로 성장 ↑ |
| Stack | 지역변수, 함수 인자 | 아래로 성장 ↓ |
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
// 문제 1: 문자열 리터럴 (Read-Only)
char *data = "This is read-only string";
// 문제 2: Heap 메모리 누수
char *name = malloc(40);
name = "andrew"; // malloc 주소를 잃어버림!
// 💥 Segmentation Fault!
strcpy(data, "hello world"); // Read-Only 메모리에 쓰기 시도
return 0;
}
char *data = "This is..."; // data는 Stack, 문자열은 Text(Read-Only)
strcpy(data, "hello"); // ❌ Read-Only 영역에 쓰기 → Crash!
해결책:
// 방법 1: 배열로 선언 (Stack에 복사본 생성)
char data[] = "This is...";
// 방법 2: Heap에 할당
char *data = malloc(50);
strcpy(data, "This is...");
char *name = malloc(40); // Heap에 40바이트 할당 (주소: 0x12345678)
name = "andrew"; // Text 영역 주소(0x00400500)로 덮어씀
// → 0x12345678은 영원히 접근 불가 (Leak!)
해결책:
char *name = malloc(40);
strcpy(name, "andrew"); // malloc 받은 메모리에 복사
// 사용 후
free(name); // 반드시 해제!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
// Stack에 배열 할당 (수정 가능)
char data[100] = "Original string";
// Heap에 메모리 할당
char *name = malloc(40);
if (name == NULL) {
perror("malloc failed");
return 1;
}
// 안전하게 복사
strcpy(name, "andrew");
strcpy(data, "hello world");
printf("data: %s\n", data);
printf("name: %s\n", name);
// 메모리 해제
free(name);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
// Data Segment (초기화된 전역변수)
int global_init = 10;
// BSS Segment (초기화 안 된 전역변수)
int global_uninit;
int main() {
// Stack (지역변수)
int local = 20;
// Heap (동적 할당)
int *heap_var = malloc(sizeof(int));
*heap_var = 30;
// Text (문자열 리터럴)
char *str = "Hello";
printf("=== 메모리 주소 ===\n");
printf("Text (str): %p\n", (void*)str);
printf("Data (global_init): %p\n", (void*)&global_init);
printf("BSS (global_uninit): %p\n", (void*)&global_uninit);
printf("Heap (heap_var): %p\n", (void*)heap_var);
printf("Stack (local): %p\n", (void*)&local);
free(heap_var);
return 0;
}
=== 메모리 주소 ===
Text (str): 0x0000000000400610 (낮은 주소)
Data (global_init): 0x0000000000601030
BSS (global_uninit): 0x0000000000601038
Heap (heap_var): 0x0000000001a4a010
Stack (local): 0x00007ffc8b3e2a4c (높은 주소)
커널 → _start → __libc_start_main → main()
ASLR로 인해 주소는 실행마다 바뀜
int main(int argc, char *argv[])
argv[0]: 프로그램 이름
argv[1~argc-1]: 실제 인수
extern char **environ; // 전체 목록
getenv(name) // 값 가져오기
setenv(name, val, 1) // 값 설정
unsetenv(name) // 삭제
자식 프로세스 변경 → 부모에 영향 없음!
Text (Code) → Read-Only! 수정 시도 시 Segfault
Data → 초기화된 전역변수
BSS → 초기화 안 된 전역변수 (0으로 자동 초기화)
Heap → malloc (위로 성장 ↑)
Stack → 지역변수 (아래로 성장 ↓)
❌ char *s = "literal"; strcpy(s, "new"); // Segfault
✅ char s[] = "literal"; strcpy(s, "new"); // OK
❌ char *p = malloc(10); p = "leak"; // Memory Leak
✅ char *p = malloc(10); strcpy(p, "ok"); free(p);
man environ, man getenv