정글에서 온 개발자
정글pintOS project3 - Anonymous page 디버깅 본문
배경
- pintos vm에서는 page를 보조테이블에 uninit_page 로 미리 넣어놓고, 페이지를 읽을 때 lazy_load_segment로 물리주소에 할당한다.
- process를 시작할 때 읽어들이는 파일도 마찬가지인데, process_exec이 stack을 쌓고 do_iret()을 하면, 해당 rip로 가면서 rip가 가리키는 가상주소를 물리주소에 할당하기 시작한다.
- 그런데 위와 같이 잘 읽어들이다가 0을 가리키는 곳으로 빠졌다.
- 해당 fault가 특정 코드에서 나는 것이 아니기 때문에 fault 직전에 브레이크포인트를 잡고 디버깅할 수도 없어 이틀 정도를 헤멨다.
- 'addr : ' log는 page_fault_handler 진입하자마자 찍는 로그이다.
해결
- 기존 코드에서는 아래 두 코드가 빠져있었다.
- load_segment로 lazy_load를 세팅해주는 동안 offset (pos)을 계속 이동 시켜주는 코드
- lazy_load segment 안에서 file_read전에 file_seek로 pos를 바꿔주는 코드
static bool
load_segment (struct file *file, off_t ofs, uint8_t *upage,
uint32_t read_bytes, uint32_t zero_bytes, bool writable) {
ASSERT ((read_bytes + zero_bytes) % PGSIZE == 0);
ASSERT (pg_ofs (upage) == 0);
ASSERT (ofs % PGSIZE == 0);
while (read_bytes > 0 || zero_bytes > 0) {
/* Do calculate how to fill this page.
* We will read PAGE_READ_BYTES bytes from FILE
* and zero the final PAGE_ZERO_BYTES bytes. */
size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
//arg 세팅
struct seg_arg *args = calloc(1, sizeof(struct seg_arg));
if(args == NULL){
PANIC("Memory allocation failed\\n");
return false;
}
args->file = file;
args->ofs = ofs;
args->page_read_bytes = page_read_bytes;
args->page_zero_bytes = page_zero_bytes;
void *aux = args;
if (!vm_alloc_page_with_initializer (VM_ANON, upage,
writable, lazy_load_segment, aux)){
return false;
}
/* Advance. */ -> Advance라고 써있어서 낚였다.
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
upage += PGSIZE;
ofs += page_read_bytes; //이 부분 추가
}
return true;
}
기본 원리
- load_segment를 하면 file을 read_bytes만큼 파일을 읽어들이는 설정을 하는데, 바로 읽지 않고vm_alloc_page_with_initializer로 lazy_load_segment를 uninit_page의 init 함수로 지정하고 넘어간다.
- 이 때 aux 값으로 해당 페이지를 파일에서 읽어올 때 어느 offset에서 읽을지 지정해줘야 한다.
- 한편 내부에서는 넘겨받은 aux값은 file_seek()를 통해서 파일에서 읽어야 하는 위치를 정해준다.
문제의 핵심
- lazy_load_segment시 읽어오는 페이지들이, load_segment 때 순차적으로 while문을 돌았던 것과 달리 순차적이 아니다.
- 그래서 segment를 읽을 때, 어떤 offset부터 읽어야 하는지 정확히 지정해야 한다.
- 최근 file_read로 바뀐 offset (pos)를 믿고 그대로 쓰게 하는 것은 정확히 지정해준 것이 아니다.
static bool
lazy_load_segment (struct page *page, void *aux) {
struct seg_arg *args = (struct seg_arg *)aux;
struct file *file = args->file;
off_t ofs = args->ofs;
size_t page_read_bytes = args->page_read_bytes;
size_t page_zero_bytes = args->page_zero_bytes;
file_seek(file, ofs); //이 부분
uint8_t *kpage = page->frame->kva;
/* Load this page. */
if (file_read (file, kpage, page_read_bytes) != (int) page_read_bytes) {
palloc_free_page (kpage);
vm_dealloc_page(page);
return false;
}
memset (kpage + page_read_bytes, 0, page_zero_bytes);
free(aux);
return true;
}
고찰
이전엔 왜 에러가 났을까?
- lazy_load는 파일의 해당 부분이 불러와질 때 frame과 연결되고, 물리 메모리에 할당된다.
- do_iret 직후에는 해당 파일의 main의 주소로 이동한다.
- 하지만 코드를 진행하면서 읽으려는 코드가 들어있는 가상주소에 해당 코드가 로드가 안 되어 있을 수 있다.
- 그런데 그 접근하는 순서가 ‘순차적이 아니다’. 중간에 함수 호출, 조건 분기 등으로 page를 넘어서 코드를 건너 뛸 수 있다.
- 예시에서는 2번째 lazy load를 하는데, 해당 코드가 0x605248에 있다.
- 이 때 file_seek 를 하지 않고 file_read를 하면, read는 마지막 file_read의 최근 offset(0x400c7f+PAGE_SIZE)부터 시작하고, 가상주소 0x605248에 들어갔어야 할 파일 내용이 아닌 최근 offset의 파일이 로드되어 예상외의 동작을 하게 된다.
- file_seek()만 넣어줬을 때도 문제가 생길 수 있다.
- 예시에서 0x605248 페이지 실행을 무사히 마치고 다시 0x401076자리로 돌아간다.
- 이 때 offset 이동 코드를 쓰지 않았다면, file의 offset은 마지막으로 file_read를 끝마친 offset( 0x604d20+PAGESIZE )을 가리키고, 가상주소 0x401076에 로드되어야 하는 파일의 내용에는 해당 offset의 내용이 로드되어서 이후에 예상하지 못한 방식으로 동작하게 된다.
미해결 의문
#include "tests/lib.h"
int
main (int argc, char *argv[])
{
int i;
test_name = "args";
if (((unsigned long long) argv & 7) != 0)
msg ("argv and stack must be word-aligned, actually %p", argv);
msg ("begin"); //msg함수 코드의 위치 0x605248?
msg ("argc = %d", argc); // 다시 0x40176?
for (i = 0; i <= argc; i++)
if (argv[i] != NULL)
msg ("argv[%d] = '%s'", i, argv[i]);
else
msg ("argv[%d] = null", i);
msg ("end");
return 0;
}
- args-one 테스트를 돌리면 실제로 돌아가는 args.c 에서, 어디에서 점프를 뛰는지 알고 싶은데, msg가 유력한 후보인 것 같다.
- msg실행 이후 다시 main 함수로 돌아와서 msg(”argc =%d”,argc) 를 실행하는 코드 (어셈블리어) 가 들어가 있는 부분이 0x40176 에 들어있는 것으로 추측된다.
- 하지만 printf(”%p”,msg); 를 해본 결과 0x40번대가 출력되었다.
- 어셈블리어의 라인을 이동하면서 디버깅 되는 툴이 있어야 해당 의문이 풀릴 것 같다
'TIL' 카테고리의 다른 글
정글 pintOS project3 - mmap. file_reopen 잡아주기 (1) | 2023.12.26 |
---|---|
정글 pintOS project3 page-linear 디버깅 (0) | 2023.12.25 |
[pintOS]-FIFO 궁금했던 점 분석 (1) | 2023.12.05 |
[pintOS] - list 라이브러리 고찰 (0) | 2023.12.04 |
pintOS - Alarm Clock. Explicit Global Tick 고찰 (0) | 2023.12.04 |