2014년 10월 29일 수요일

Vim 파일 저장 오류 __ 9

stat() 함수는 왜 실패했을까?
Ubuntu에서 stat() 함수에 애초부터 문제가 있었던 건 아닐까?

간단하게 C program을 만들어서 이를 시험해 보기로 했다.

test.c
stat()의 대상 파일은 DOSBox에서 시험을 했던, 디버그 메시지로 출력된 그 파일을 사용했다.
출력 메시지에 자세한 정보를 넣으려 했더니 이상한 에러가 발생해서, 간단하게 성공 실패만 출력하게 만들었다.

빌드하고 시험해 보니....성공...(허무-_-)
test 결과
그렇다면....Ubuntu에서 stat() 함수는 정상적으로 작동한다고 정정해야 겠다.
그렇다면 DOSBox에서는 왜 실패했던걸까?
인자로 전달된 파일 이름에 무언가 보이지 않는 문제가 있었단 말인가?
그 문제를 확인하려면 실제 인자로 전달되는 메모리를 살펴봐야 하지 않을까 싶다.
그러기 위해서는 DOSBox를 디버깅해봐야 한다는 얘기가 되고...
DOSBox의 자체 디버거로 이것이 가능할까?

=================================================================
잠깐, 앞서 언급했던 이상한 에러에 대해서 살펴보자.


test.c에서 printf()에 변수를 출력하기 위해 포맷문자(%)를 사용하면 문제가 생기는 것이었다.
앞선 소스에서 주석처리를 했던 부분을 다음과 같이 다시 살려놓고 빌드해 보았다.
test.c
컴파일에서는 아무런 문제가 없었지만 링킹 과정에서 문제가 생긴 듯한데, 처음보는 이상한 오류를 내놓았다.
이게 ld의 문제라는 것인지 collect2의 문제라는 것인지도 모호하다.
test.c 빌드 에러
google에 오류 메시지를 그대로 붙여넣고 검색해 보니 꽤 많은 결과들이 검색되기는 하지만 딱히 이 경우에 해당되는 문서는 아직 찾지 못했다.
빌드 에러 검색
문제 하나도 해결 못하고 여기까지 왔는데, 이상한 에러까지 더해지니 머리가 복잡해지려고 한다.
일단은 본래의 문제에만 집중하기로 하고, 이 문제는 잠시 접어두기로 하자.
=================================================================
문제 해결!
아주 간단하면서도 어이 없는 실수에 의한 문제였는데,
실패했을 경우를 위해 추가했던 [extern int errno;]와 관련이 있는 문제였다.
외부에 있다고 지정한 errno를 찾지 못해서 에러가 난 것.
이 문제를 해결하려면 [#include <errno.h>]를 추가해 주면 된다.
창피한 실수이긴 하지만, 이런 경우의 에러 메시지를 내지 못하고 세그멘테이션 폴트가 나온다는 것도 이상하지 않냐고 반문해 보고 싶다.
=================================================================

이제 본류로 다시 돌아와서 디버깅에 대해 생각을 해보자.
하지만 오래 생각할 것도 없이, 이 디버깅은 DOSBox의 내장 디버거로는 불가능한 것이다.
DOSBox의 내장 디버거가 다룰 수 있는 범위는 DOSBox 위에서 돌아가는 프로그램들에 국한이 된 것이고, 이 문제는 DOSBox와 Ubuntu에 걸쳐 있는 문제이므로, 아얘 다른 도메인(?, 세상, 차원)에 속하는 것들인 셈이다.
세상 어떤 프로그램이 자기 자신을 완전히 디버깅 할 수 있단 말인가?
세상 어딴 프로그램이 자신의 기반이 되는 모(母)프로그램을 완전히 디버깅 할 수 있단 말인가?
재미있지 않은가?
따지고 보면 인간도 자기 자신을 완벽히 비판하고 파악하는 것은 불가능하다는 말이 되지 않을까?

간단히 말하면, 그래서 gdb를 써야 한다는 것이다.
지금 빌드해 놓은 DOSBox가 내장 디버거를 활성화 시킨 것이라 gdb에서 DOSBox를 바로 호출하게 되면 gdb와 DOSBox의 내장 디버거가 뒤엉킬것이므로, 먼저 DOSBox를 실행시킨 후에 gdb를 attach하기로 했다.
ps로 알아낸 프로세스 번호는 2400, --pid 옵션으로 gdb에 attach 시킨다.
gdb attach
DOS_SetFileAttr()에 breakpoint를 설정한다.
문제가 되었던 localDrive::GetFileAttr()에 설정할 수도 있겠지만 원하지 않을 경우에도 break가 걸리게 될 것이므로...
set breakpoint
continue로 DOSBox를 실행시키고, vim에서 파일을 불러온 후에 수정하고 저장을 시도한다.
그러면 설정해 두었던 breakpoint에서 멈추게 된다.
이제 문제가 되었던 stat() 지점까지 n(ext)와 s(tep)으로 진행을 한다.
next/step
stat()을 호출하기 직전에 인자로 사용되는 변수 newname의 값이 어떤지 확인해 본다.
gdb의 p(rint)로 문자열을 확인하면 모든 값들이 표시된다고 보면 될 것이다. 출력 불가능한 문자들의 경우에는 "\nnn "으로 표시해 주니까 중간에 이상한 문자가 끼었는지는 금방 알 수 있다.
확인해 보니 변수값에 이상한 문자가 없음을 확인할 수 있다.
next/step/print
자세히 보면 그냥 NEWFILE이 아니라 NEWFILE.~임을 알 수 있다.
지난 번의 DOSBox 디버그 출력에도 있었는지 모르겠지만, vim에서 파일을 저장할 때 원본 파일과 <원본.~>파일 두개를 동시에 저장하려 하는 것으로 보인다.
step
그런데 stat()에서 s(tep)으로 진입을 해 보니, 인자의 값이 newname이 아닌 name의 값으로 표시가 된다.???
계속해서 s(tep)으로 들어가 보지만 더 이상 깊이 들어갈 곳은 없어 보인다.
c(ontinue)로 이어서 진행을 하자 이번에는 원본파일인 NEWFILE로 다시 한 번 break가 걸린다.
next/step again
이전과 같이 n(ext)와 s(tep)으로 stat()까지 진행.
앞선 경우처럼 stat()에 사용된 인자가 바뀌었을까 이상해서 어셈블 코드를 확인해 보기로 했다.

"disassemble /m"으로 소스와 어셈블 코드를 표시해 본다.

일단 어셈블 코드가 좀 낯설다.
일단 대충 살펴보니, stat()을 호출하는 부분이 보이질 않는다.
단지 test %eax, %eax와 jne 0x80decc0
0x80decc0에서 stat()을 호출하는 걸까?
disassemble
0x80decc0는 stat()의 처리가 실패했을 경우에 처리되는 부분이다.
attr에 0을 넣고 디버그 메시지를 출력하는 코드로 이어진다.
그렇다면 stat()을 호출하는 곳은 어디에 있단 말인가?
앞의 어셈블 코드를 다시 살펴보니 stat()을 전후로 디스어셈블 되지 않은 번지들이 있음을 알게 되었다. 즉, 0x80dec66에서 0x80dec85로 약 0x1F(31) 바이트 정도가 보이질 않는다.
disassemble (계속)

disassemble (계속)
그래서 강제로 번지수를 지정해서 디스어셈블 출력을 해 보았다.
/m은 mixed로 소스와 함께 출력해야 하므로 출력이 제대로 되지 않고 /r은 hex data까지 표시되어 난잡해 보이니 아무 옵션 없이 출력하는 게 젤 나아 보인다.
disassemble (추가)
하지만 역시나 Linux의 어셈블러 표기에 익숙하지 않아서 혼란스럽고 이해가 잘 안된다.
Linux 어셈블러 공부마조 또 해야 하나?ㅠㅠ

댓글 없음: