본문 바로가기

[pintOS] ubuntu 22.04에서 로드 버그가 발생할 때

@정소민fan2025. 5. 21. 15:32

버그 발생

크래프톤 정글에서 pintOS 과제의 권장 우분투 버전은 18.04이다. 하지만 18.04에서 vscode로 원격 접속하려면 vscode의 버전도 낮춰야 하는 등 불편한 점이 많다. 그래서 나는 22.04를 그대로 사용하고 있었는데, 첫번째 과제에서는 분명히 문제없이 모든 테스트가 통과했었다. 하지만 두번째 사용자 프로그램 과제에서는 로드에서 버그가 터지는 것이었다.

 

먼저 로드 코드를 보자

/* Loads an ELF executable from FILE_NAME into the current thread.
 * Stores the executable's entry point into *RIP
 * and its initial stack pointer into *RSP.
 * Returns true if successful, false otherwise. */
static bool
load (const char *file_name, struct intr_frame *if_) {
	struct thread *t = thread_current ();
	struct ELF ehdr;
	struct file *file = NULL;
	off_t file_ofs;
	bool success = false;
	int i;

	/* Allocate and activate page directory. */
	t->pml4 = pml4_create ();
	if (t->pml4 == NULL)
		goto done;
	process_activate (thread_current ());

	/* Open executable file. */
	file = filesys_open (file_name);
	if (file == NULL) {
		printf ("load: %s: open failed\n", file_name);
		goto done;
	}
    ...

로드 함수의 일부분이다. elf 바이너리 파일을 열고, 읽어서 각 세그먼트를 배치해주는 역할을 한다.

로드 함수 자체를 해석하기에는 옛 과제라 그런지 변수명도 알아보기 힘들고, 로딩을 깊게 공부한것도 아니라서 힘드니까 넘어가자.

그럼 22.04에서는 어느 부분에서 문제가 발생하냐?

/* Read program headers. */
	file_ofs = ehdr.e_phoff;
	for (i = 0; i < ehdr.e_phnum; i++) {
		struct Phdr phdr;

		if (file_ofs < 0 || file_ofs > file_length (file))
			goto done;
		file_seek (file, file_ofs);
        ...
        			if (!load_segment (file, file_page, (void *) mem_page,
								read_bytes, zero_bytes, writable))
						goto done;
				}
				else
					goto done;
				break;
		}
	}

이렇게 각 elf 섹션들을 for문을 돌면서 메모리에 적재해주는 도중에, 0x4000000 이라는 같은 가상 주소에 서로 다른 elf 섹션을 배치하려고 해주는 것이다 !!

static bool
load_segment (struct file *file, off_t ofs, uint8_t *upage,
		uint32_t read_bytes, uint32_t zero_bytes, bool writable) {
        ...
        /* Add the page to the process's address space. */
		if (!install_page (upage, kpage, writable)) {
			printf("fail\n");
			palloc_free_page (kpage);
			return false;
		}
        ...
}

load_segement 내에서 인자로 받은 특정 사용자 가상 주소 (upage)에 커널에서 할당한 실제 물리 페이지(kpage)를 매핑하는 install_page를 호출한다.

install_page도 들여다보자.

static bool
install_page (void *upage, void *kpage, bool writable) {
	struct thread *t = thread_current ();

	/* Verify that there's not already a page at that virtual
	 * address, then map our page there. */
	return (pml4_get_page (t->pml4, upage) == NULL
			&& pml4_set_page (t->pml4, upage, kpage, writable));
}

이 함수 내부에서는 먼저 이미 해당 가상 주소에 매핑된 페이지가 있는지 확인 (pml4_get_page)하고, 매핑되어있지 않다면 실제 매핑을 시도한다 (pml4_set_page).

 

따라서 같은 가상 주소를 배치하려고 시도했기에 여기서 false를 리턴하고, install 페이지에서도 false를 연속으로 리턴하게 되는 것이다.

원인이 무엇인지 정리해둔 블로그 글이 있다. 참고하자.

https://just-live.tistory.com/entry/pintos-troubleshooting

 

[Pintos] 트러블슈팅: 기본 제공 함수에서 오류가 발생하는 문제 해결하기

트러블슈팅: 기본 제공 함수에서 오류가 발생하는 문제 해결하기이번에 Pintos 프로젝트를 진행하면서 새롭게 수정하지 않은 기본 제공 함수에서 오류가 발생하는 상황을 겪게 되었는데요. 그 사

just-live.tistory.com

 

해결방법

1. 우분투 버전을 18.04로 낮춘다.

근데 난 이건 죽어도 싫었다. 너무 번거롭고 귀찮다. 학부 때 교수님이 말씀하시길, 개발자들은 게을러야 한다고 했다. 게으르고 귀찮은 거 싫어하니까 편의 함수와 라이브러리를 계속 만들어내는 거겠지

 

2. 코드를 새로 추가한다.

GPT를 활용해서 22.04에서도 로딩이 되도록 하는 코드를 찾았다.

	...
    	file_ofs = ehdr.e_phoff;
	for (i = 0; i < ehdr.e_phnum; i++)
	{
		struct Phdr phdr;

#ifdef WSL
		// 22.04 전용 코드
		off_t phdr_ofs = ehdr.e_phoff + i * sizeof(struct Phdr);
		file_seek(file, phdr_ofs);
		if (file_read(file, &phdr, sizeof phdr) != sizeof phdr)
			goto done;
#else
		// 18.04 전용 코드
		if (file_ofs < 0 || file_ofs > file_length(file))
			goto done;
		file_seek(file, file_ofs);
#endif

		if (file_read(file, &phdr, sizeof phdr) != sizeof phdr)
			goto done;
		file_ofs += sizeof phdr;
        ...

for문 내부에 이렇게 이렇게 수정해두자 !!

 

사실 무슨 원리로 똑바로 동작되는지 알 수가 없다. WSL이 정의되면 file_read가 두번 실행되는거 아니냐고 할 수도 있는데, 없으면 로드 버그가 나니까 그냥 두자

 

ifdef 으로 추가해둔 이유는, 팀원들은 18.04를 사용중이고 나는 22.04를 사용중인데 각각 로드가 되는 코드가 다르기 때문이다. 나 하나 로드 버그 피하자고 그냥 22.04 전용 코드를 딱 추가해버리면 git으로 관리되는 우리 팀원들의 코드는 대체 어떻게 하나?? 따라서 make 때마다 인자를 줘서 서로 다른 코드로 빌드되도록 분리해둔 것이다.

사실 이 방법을 생각해내긴 했지만 대체 어떻게 인자를 추가해야될지는 몰라서 pintos 조교님에게 질문을 했더니...

이렇게 직접 스크립트 만들어서 주셨다 !! 갓갓

자 먼저 userprog 밑의 Make.vars를 복사해서 Make.vars.flag를 만들자

그 다음 내용을 이렇게 수정하자

# -*- makefile -*-

os.dsk: DEFINES = -DUSERPROG -DFILESYS -DWSL # 여기에 -DWSL만 추가되었다 !!
KERNEL_SUBDIRS = threads tests/threads tests/threads/mlfqs
KERNEL_SUBDIRS += devices lib lib/kernel userprog filesys
TEST_SUBDIRS = tests/userprog tests/filesys/base tests/userprog/no-vm tests/threads
GRADING_FILE = $(SRCDIR)/tests/userprog/Grading.no-extra

# Uncomment the lines below to submit/test extra for project 2.
# TDEFINE := -DEXTRA2
# TEST_SUBDIRS += tests/userprog/dup2
# GRADING_FILE = $(SRCDIR)/tests/userprog/Grading.extra

그 다음 userprog에서 run.sh를 만들어서 추가하자. 그리고 다음 내용을 붙여넣자

#!/bin/bash

if [ $# -ne 1 ]; then
  make -j
else
  mv Make.vars Make.vars.old
  cp Make.vars.flag Make.vars
  make -j
  rm Make.vars
  mv Make.vars.old Make.vars
fi

정확히 무엇을 하는 코드인지는 모르겠지만 아마 make 시에는 Make.vars를 사용하는데, 이 스크립트를 실행하면 Make.vars가 Makr.vars.flag로 교체되어 make를 진행하게 되어 인자를 주게 되는 것 아닌가 추측된다.

 

그 다음 chmod +x run.sh로 스크립트에 실행 권한을 주자.

그리고 ./run.sh flag로 이 스크립트를 실행하게 되면 (반드시 뒤에 인자를 아무거나 하나 줘야한다) wsl이 인자로 들어오게 되고, wsl 전용 코드가 활성화되게 된다. 그러면 로드 버그 없이 테스트를 진행할수 있다 !!

 

pintos 조교님에게 무한한 감사를 !

정소민fan
@정소민fan :: 코딩은 관성이야

코딩은 관성적으로 해야합니다 즐거운 코딩 되세요

목차