kernel mode
User mode
=> 기본적인 I/O protection을 하기 위함. 악의적 사용자가 input으로 특정 하드웨어 메모리에 명령을 내리는 assembly어를 썼더라도 이를 user mode에서 읽게되면 cpu가 해당 명령을 수행하지 않음.
PEB 는 프로세스 정보를 담고있는 구조체 입니다.
PEB구조체는 아래와 같이 정의되어 있다.
typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
PVOID Reserved4[3];
PVOID AtlThunkSListPtr;
PVOID Reserved5;
ULONG Reserved6;
PVOID Reserved7;
ULONG Reserved8;
ULONG AtlThunkSListPtr32;
PVOID Reserved9[45];
BYTE Reserved10[96];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
BYTE Reserved11[128];
PVOID Reserved12[1];
ULONG SessionId;
} PEB, *PPEB;
PID는 운영체제에서 프로세스를 식별하기위해 프로세스에 부여하는 번호를 의미한다.
PPID는 부모의 프로세스를 의미한다.
User mode에서 실행되는 user process의 stack 데이터 영역.
일반적으로 user mode에서의 함수 콜은 user stack에 모두 쌓인다.
하지만 system call 의 경우는 kernel 함수이긴 하지만 user memory의 stack 에 쌓지 않고 Kernel memory 있는 stack에 쌓이게 된다.
즉, process 가 User mode에서 실행될때는 User mode Stack을 사용하게 되고
Kernel mode에서 실행 될때는 Kernel mode stack에 쌓이게 된다.
원론적인 의미의 calling convention이란 호출자caller와 피호출자 callee간의 함수의 인자를 전달하는 방식에 대한 규약을 정의한 것. 이러한 호출규약은 architecture 마다 다를 수 있음.
x86_64 calling convention
메모리 계층구조는 다음과 같다.
이중 레지스터는 cpu와 딱붙어있어 가장 빠르지만 메모리 중엔 가장 작은 용량을 가진다.
레지스터는 맡은 역할에 따라 담은 데이터가 다르다.
메모리가 슈퍼마켓이라면 레지스터는 냉장고!
요리(연산)하는데 필요한 재료는 슈퍼마켓(메모리)에서 사와서 냉장고(레지스터)에 넣어놓는다. 그리고 필요할때마다 그때 그때 꺼내서 쓴다.
범용 CPU레지스터의 종류는 다음과 같다.
프로그램에 인자값, 즉 main()에 인자값을 전달해야할때 써먹을 수 있는 요소다.
main함수의 원형은 다음과 같다.
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
int i;
for (i = 0; i < argc; i++)
printf("argv[%d]: %s\n", i, argv[i]);
exit(0);
}
출처: https://gday2code.tistory.com/72 [우리들의 지식저장소]
argc 는 argument count로 int형으로 정의하며, 인수들의 개수이다.
argv 는 argument vector이며 char형 더블포인터이다. 인수들 자체를 나타내는 문자열들의 배열이다. (문자열의배열이니 더블포인터겠지요?)
즉, argv는 입력하는 문자열의 전체를 나타내고 argc는 해당 문자열의 요소 개수 정도로 생각하면 될듯 하다.
단순하게 말하면 그냥 리눅스/유닉스의 파일 실행에서 디스크에 저장되어있던 프로그램이 메모리 영역에 올라가서 컴퓨팅 자원을 사용하여 서비스 제공해주는 것.
ELF에서는 프로그램이 실행될때 메모리에 올라가야 할 각각의 부분들을 미리 정리하여 관리하다가 실행을 하게되면 정리된 부분들(code, data, bss)을 메모리에 올리게 된다.
이렇게 메모리에 올라온 주소공간의 code 영역의 instruction을 한라인씩 실행하며 프로세스가 진행된다.
ELF 파일 형식
1) ET_NONE (ELF type none) (0x 0000)
: 알 수 없는 형식 -> 아직 정의되지 않았거나 알 수 없다는 의미
2) ET_REL (ELF type relocatable) (0x 0001) - 링킹 가능한 포맷
: 재배열이 가능한 파일 형식 -> 아직 실행파일에 링킹되지 않은 상태
(*.o 파일로 컴파일만 진행된 상태)
3) ET_EXEC (ELF type executable) (0x0002) - 실행 가능한 포맷
: 실행 파일 형식(= 프로그램) -> 프로세스의 시작 지점인 엔트리 포인트가 존재
4) ET_DYN (ELF type dynamic) (0x0003) - 실행 가능하면서 링킹 가능한 포맷
: 공유 오브젝트 파일 형식 -> 동적 링킹이 가능한 오브젝트 파일 (= 공유 라이브러리)
(런타임 중에 프로그램의 프로세스 이미지로 로드되고 링크 됨)
5) ET_CORE (ELF type core) (0x0004)
: 코어 파일 형식 -> 프로세스 이미지의 전체 덤프
주로 프로그램이 비정상 종료되거나 SIGSEGV 시그널(세그멘테이션 폴트)로 인해 프로세스가 종료된 경우 생성
GDB로 코어파일을 읽어 분석해 비정상 종료된 시점의 원인 분석이 가능
ELF는 2가지 View 를 가진다
Linking View , Execution View.
Linking is the process that make codes and data to combine one object(file.o).
In combining process, 2 process will event.
1. Relocation.
-> This is allocating memory address of each part of program when they're loading.
2. Symbol Resolution.
-> This step helps caller who call function of another programs to call easily without knowing exact address of functions.
Loader is the process which loading files saved in disks to the memory.
정확하게는 메모리에 로드 될 수 있는 포맷의 code와 data들을 메모리에 로딩하고, program의 시작주소로 이동하는 프로그램이다.
loader는 execve 함수로 호출되는 프로그램이다.
프로세스가 현재 사용중인 파일을 관리하기 위한 테이블이며, 프로세스마다 하나씩 가지고 있다. File Table을 가리키는 포인터를 담고있는 배열이고, 이 배열의 index 가 fd이다.
이런식의 도식화 또는
table 만으로 표현하면 이렇다.
잘 정리해주신 블로그가 있어 링크 붙인다.
Pintos 의 Table 구조
Basic Page Table Mapping
보호모드로 전환된 후의 접근가능한 virtual memory의 크기는 4GB이고, pintos는 이중에서 0xc0000000-0xffffffff(3GB-4GB) 의 1GB 공간을 커널 영역으로 사용한다. (이 커널의 시작 위치는 LOADER_PHYS_BASE(0xc0000000) 라는 값으로 <thread/loader.h> 에 정의되어 있다.)
기본적인 Page Table 을 만들어, 현재 Physical Memory address 를 Virtual Memory 의 0-64MB 에 그대로 mapping한다.
동일한 Physical Memory address를 Virtual Memory의 LOADER_PHYS_BASE에 mapping한다.
- 보호모드로 바뀐 후에는 Physical Memory 의 0-64MB 의 공간에 직접 접근해서 사용하는 것이 불가하므로 LOADER_PHYS_BASE 를 더한 커널 영역의 Virtual address 에 접근하여 사용하게 하기 위함이다.