상세 컨텐츠

본문 제목

PintOS | Project3. Virtual Memory | 트러블 모음 2

Computer Science/운영체제 (Operating System)

by hyuga_ 2023. 12. 27. 11:06

본문

Stack growth 트러블 모음

pt-write-code2 테스트 케이스

pt-write-code2 테스트는 잘못된 영역(코드 세그먼트)에 read() 시도를 하는 테스트이다.

  • 메모리 관점에서 보면 read() 시스템콜은 파일의 데이터를 읽어서 메모리의 버퍼에 쓰는 작업을 포함한다.
  • 그러나 현재는 코드 세그먼트는 not writable 해야 하는데, read()가 되고 있는 상황, 즉 not writable한 페이지에 write가 되는 상황이다.

 

문제상황을 실제 파일에서 확인해보았다.

 

pt-write-code2 테스트 코드는 다음과 같으며, 

void
test_main (void)
{
  int handle;

  CHECK ((handle = open ("sample.txt")) > 1, "open \\"sample.txt\\"");
  read (handle, (void *) test_main, 1);
  fail ("survived reading data into code segment");
}

 

output 파일을 보면 다음과 같이 출력되고 있다.

 

 

 

이를 해결하기 위해 read() 시스템 콜 내부에 다음과 같은 코드를 추가하였다.

  • buffer(사실상 address)를 토대로 해당 page를 찾는다.
  • 해당 page의 writable 필드를 체크하여, not writable하다면 exit(-1).
int read (int fd, void *buffer, unsigned length) {	
	struct page *page = spt_find_page(&thread_current()->spt, buffer);
	if (page) {
		if (!page->writable) {
			exit(-1);
		}
	...
}

 

pt-grow-stk-sc 테스트 케이스

pt-grow-stk-sc 테스트 케이스는 첫 스택접근이 syscall에 의한 것일 때도 스택이 올바르게 확장되는가를 체크한다.

결론부터 말하자면, read() 시스템 콜에서는 check_address(buffer)를 직접 호출하지 않고, 꼭 필요한 주소 유효성 검사 로직만 일부 추가하는 방식으로 수정하였다. 그 결과 테스트케이스를 통과할 수 있었다.

 

 

기존 read() 함수는 바로 check_address로 주소의 유효성을 검사하게끔 설계되어 있다.

int read (int fd, void *buffer, unsigned length) {
	check_address(buffer);
	...
}

 

check_address() 함수는 Anonymous Page에서 수정한 함수인데, 어떻게 짰었는지 다시 한번 보자.

void check_address(void *addr) {
	struct thread *t = thread_current();
	/* [기존 코드: 정상적인 lazy loading 에서도 에러 검출되는 구조]

	if (!is_user_vaddr(addr) || addr == NULL || pml4_get_page(t->pml4 , addr) == NULL) {
		exit(-1);
	}
	*/

	/* [수정 코드: lazy loading이라 pml4에 없는 상황 고려] */
	if (!is_user_vaddr(addr) || addr == NULL) {
		exit(-2);
	}
	
	if (pml4_get_page(t->pml4 , addr) == NULL){
		// spt에 있으면 정상적인 lazy loading 상황 -> 에러 아님!
		if(!spt_find_page(&t->spt, addr)){
			exit(-3);
		}
	}
}

 

(exit(-2), exit(-3)은 디버깅을 위해 임시로 바꾼 것이다. 원래 -1이어야 한다.)

 

이렇게 둔 상태로 pt-grow-stk-sc.output 파일의 결과를 확인해보면,

 

 

 

stack 영역에 해당하는 페이지는 스택 확장시에 spt에 추가해주게 되는데, 아직 스택 확장하기 전에 (즉, spt에 페이지가 들어오기 전에) 에러 검출을 하고있기 때문에 발생하는 문제였다.

 

 

내가 생각한 방법은 두 가지였다.

  1. check_address()에서 spt에 해당 페이지가 존재하는지 검사하는 로직을 지우기
  2. read()에 한해서, check_address(buffer)를 지우고 spt 검사를 제외한 나머지 부분을 따로 추가해주기

테스트해본 결과 둘 다 가능했지만 검사를 덜 하는 1번보다 상황에 맞게 에러 검출을 시도하는 2번이 더 좋은 방법이라 생각해 2번으로 수정을 진행했다.

int read (int fd, void *buffer, unsigned length) {
	// check_address(buffer); 삭제
	
	// 주소가 user 영역인지, null 값은 아닌지 정도만 유효성 검사를 해준다.
	if (!is_user_vaddr(buffer) || buffer == NULL) {
		exit(-1);
	}

	...
}

 

결과적으로, 위의 두 가지 테스트 케이스(pt-write-code2, pt-grow-stk-sc)를 반영한 후의 read() 함수는 다음과 같이 바뀌었다.

int read (int fd, void *buffer, unsigned length) {	
	/* 	
	[pt-write-code2 테스트]
	- 잘못된 영역(코드 세그먼트)에 read() 시도를 한다. 
	- 메모리 관점에서 보면 read() 시스템콜은 파일의 데이터를 읽어서 메모리의 버퍼에 쓰는 작업을 수행
	- 즉, not writable한 페이지에 write를 시도하고 있다. 
	=> 해당 addr 페이지의 writable 여부를 체크해주고, exit(-1)을 반환해야 한다.
	*/ 
	struct page *page = spt_find_page(&thread_current()->spt, buffer);
	if (page) {
		if (!page->writable) {
			exit(-1);
		}
	}

	/* 
	[pt-grow-stk-sc 테스트]
	- 첫 스택접근이 syscall에 의한 것일 때도 스택이 올바르게 확장되는가를 체크한다.
	- check_address(buffer) 내부의 spt 검사에서 문제가 발생함. 
	=> check_address()를 삭제해주고, 주소가 user 영역인지, null 값은 아닌지 정도만 유효성 검사를 해준다.
	*/
	if (!is_user_vaddr(buffer) || buffer == NULL) {
		exit(-1);
	}

	... 기존 코드 ...
}

 

 

관련글 더보기