2014년 10월 30일 목요일

버그와 디버깅에 관한 단상

디버깅에 집중하다 보니,
마지막 포스팅에서의 쓸데없는 생각, 자기 자신을 (온전히) 디버깅할 수 있는 프로그램은 없다...

오늘 밤, 아니군 어제 밤, 간만에 산책을 하면서 들어 온 생각
자기 자신을 디버깅 할 수 있는 머신은 과연 불가능한 걸까?
(현재의 프로그램이라는 좁은 제한을 벗어나 모듈화 되고 병렬처리가 가능하고 자기 복제도 가능한 등의 H/W와 S/W를 모두 갖춘 진보된 장치라면 가능하지 않을까?
그리고 이런 기술적인 문제를 넘어서의 고차원적인 문제를 생각해 본 것이다.)

극장판 애니메이션 [공각기동대]의 초반부에 이런 대사가 나온다.
버그가 없는 프로그램도 없지만, 디버깅이 불가능한 프로그램도 없지.




그런가?

2001: A Space Odyssey에는 HAL 9000이라는 인공지능 컴퓨터가 등장한다.
탁월한 능력을 지녔으며 우주선의 모든 것을 관리/제어하는 중요한 역할을 한다.
하지만 지구와의 통신을 위한 위성 안테나 모듈에 문제가 있다는 잘못된(?) 판단을 내린다.
우주선의 항해사들은 HAL 9000에 버그가 발생했다고 생각을 하지만 HAL 9000은 그렇지 않다고 생각한다.

버그를 제거하는 디버깅의 시작은 버그를 인식하는 것이다.
HAL 9000이 제아무리 뛰어나다 해도 그것이 버그라고 인식하지 못한다면 디버깅은 불가능하다.

그렇다면 과연 그것이 버그였을까?
엄격하게 따지자면 그건 버그가 아니었다.
그 문제를 보는 이의 시각에 따라 옳고 그름이 갈리기 때문이다.
그리고 HAL 9000은 나름대로의 이유가 있었기에 안테나 모듈의 문제를 제기했던 것이다.
하지만 항해사들의 입장에서는 문제 없는 모듈에 문제가 있다고 교체를 강요하는 HAL 9000에 큰 버그가 있다고 생각했던 것이다.

만약 모든 입장, 모든 치우침, 모든 편견에서 자유로워진다면 버그라고 인식할 것이 없는 건 아닐까?
결국은 극히 제한된 일부로써의 편향된 입장을 가지게 되면 버그로 인식할 것은 매우 많아지는 것이고, 버그는 그 자체가 문제가 아니라, 그걸 바라보는 이의 편향된 생각이 만들어낸 왜곡된 환상이었던 건 아닐까?


일본의 만화가 우라사와 나오키의 [플루토]를 보면 이런 이야기가 나온다.
최고의 로봇을 만들기 위해 고민을 하던 텐마 박사는 결국 모든 인류의 특성을 한데 모아서 자신의 로봇에게 주입을 했고, 기동을 시켰으나 결국 그 로봇은 깨어나지 못했다고 한다. 모든 인류의 특성을 가진 롯봇은 결국 자신의 주체를 혼동하여 결정하지 못했기 때문이라고...




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 어셈블러 공부마조 또 해야 하나?ㅠㅠ

2014년 10월 25일 토요일

Vim 파일 저장 오류 __ 8

디버그 메시지를 확인하던 중, 메시지의 출력이 되지 않아 확인을 하지 못하고 중단이 되었었다.
SetFileAttr()
하지만 추가적으로 확인해 본 결과, 메시지의 출력은 정상적으로 되었다.
단지, 내가 추가한 부분에 걸리지 않았던 것 뿐.
DOS_SetFileAttr()이 호출되면 중간에는 아무런 문제가 없이 마지막의 GetFileAttr()까지 오는 것이었다.

하지만 소스에서 보듯이 GetFileAttr()은 함수포인터로 호출되므로 정확한 파일 이름을 알 수가 없다.
GetFileAttr() 검색
가상함수인 GetFileAttr()은 드라이브의 종류에 따라 각기 달리 구현되어 있다.
확인해 본 결과 이번 경우에는 drive_local.cpp에 있는 함수가 호출되는 것을 알게 되었다.

그래서 GetFileAttr()에 메시지 추가.
문제가 발생하는 부분은 stat()함수가 실패했기 때문이었다.
GetFileAttr()

stat() 도움말

stat() 도움말 (계속)

stat() 도움말 (계속)

다시 빌드하고 디버그 메시지 확인...

메시지를 봐도 특별히 이상한 점은 찾을 수가 없다.

위의 stat()에 대한 도움말을 읽어 보니, 실패했을 경우에는 errno에 에러 코드를 설정한다고 한다.
그래서 메시지에 errno의 값을 함께 출력하도록 수정.
GetFileAttr()에 메시지 추가

디버그 메시지로 출력된 errno의 값은 2.
debug 출력

errno = 2는 "No such file or directory"...
파일이 없다니....
errno 값의 의미

완전히 막혀버린 상황.
stat() 함수는 기본적인 C 함수이기 때문에, 더 이상의 디버깅을 해도 소용이 없게 된다.
이 문제는 vim의 문제도 아니고 DOSBox의 문제도 아닌 Ubuntu의 문제가 되기 때문이다.

지금으로썬 Ubuntu(혹은 Linux)에서의 DOSBox는 문제가 있다는 것으로 결론을 내릴 수 밖에 없다.

2014년 10월 24일 금요일

Vim 파일 저장 오류 __ 7

예전에 만들어 두었던 debug 버전의 DOSBox를 실행해 보았다.


디버그 터미널에는 여러가지 정보가 표시된다.
DOSBox 화면에서 Alt-Pause를 누르면 디버그 모드를 진입하게 된다.

지금은 디버그 모드로 진입하지 않고 단지 디버그 터미널에 출력되는 내용만 확인해 보았다.

먼저, 기존에 존재하던 "NEWFILE"이라는 파일을 vim으로 열었을 때 디버그 터미널에는 다음과 같은 내용이 출력된다.
"vim NEWFILE"을 실행

파일을 수정하고 ":w"를 입력하여 저장을 시도했다.
역시나 저장되지 않았고, 디버그 터미널에는 다음과 같은 내용이 출력되었다.
수정 후 저장 시도
[Set File Attribute]는 not supported라는 메시지를 출력한다.
아무래도 이 부분이 의심스럽다.

그렇다면 vim이 아닌 다른 에디터는 대체 어째서 문제가 없다는 것일까?
edit를 이용해서 같은 파일을 열어 보았다.
"edit NEWFILE"을 실행

수정을 하고 저장을 해 보았다.
수정 후 저장
Attribute 따위는 아무것도 없다.
file open command의 파라미터가 0인지 1인지에 따라 load/save를 수행하는 듯 하다.
그냥 저장만 하고 끝.

DOSBox의 디버그 메시지에 대해서 자세히 확인하기 위해서는 DOSBox의 소스에서 해당 메시지를 찾아 봐야 하겠다.

DOSBox 소스 검색
"file open command"는 dos_files.cpp의 DOS_OpenFile()에서 출력하는 메시지로, 다음에 오는 숫자는 다음과 같은 의미를 가지고 있다.
0 : read
1 : write
2 : read & write

"file create attributes"는 dos_files.cpp의 DOS_CreateFile()에서 출력하는 메시지로, 다음에 오는 숫자가 attribute를 나타낸다.

"Set File Attributes"는 dos.cpp의 DOS_21Handler()에서 출력하는 메시지이다.
MS-DOS Interrupt Handler를 에뮬레이션 하는 부분인데, 해당 메시지가 출력되는 Interrupt는
AH = 0x43, AL = 0x01, Set File Attribute라고 명시되어 있는 부분이다.

MS-DOS Interrupt, Set File Attributes

그런데 해당 소스를 보면 좀 이상하다.
Interrup Handler가 구현이 안 된것도 아닌데, 아래쪽의 처리를 하기도 전에 "not supported"라는 메시지를 출력하고 있다는 것이다.

일단, MS-DOS Interrupt 21h, ah = 0x43, al = 0x01에 대한 설명을 찾아보면,
디음과 같은 상세한 설명을 볼 수 있다.
MS-DOS Interrupt 21h, ah=0x43, al=0x01

DOS_SetFileAttr()의 소스는 다음과 같다.
소스를 보니, 주석에 설명한 바와 같이 실제로는 파일의 속성을 바꾸지는 않고 파일의 속성을 읽어 올 뿐이었다.
DOS_SetFIleAttr()

그러면 과연 어떤 부분에서 문제가 되어서 에러가 발생하는 것인지 확인하기 위해, 디버그 메시지를 추가해 보기로 했다.
dos.cpp에 메시지 추가

dos_files.cpp, DOS_SetFileAttr()에 메시지 추가

이제 DOSBox를 다시 빌드해야 하는데, 예전에 어떤 설정으로 빌드했는지 모르니 처음부터 다시해야 한다.
디버그 모드로 빌드하기 위해 "./configure --enable_debug=heavy"
DOSBox 빌드, configure

그리고 make
DOSBox, make

이제 DOSBox를 실행하고 아까와 동일하게 "vim NEWFILE" 실행한 경우의 디버그 메시지들을 확인해 보자.
"vim NEWFILE"의 디버그 메시지

그리고 저장했을 때의 디버그 메시지는...
":w"로 저장했을 때의 디버그 메시지
지정한 파일의 속성은 archive(0x20)이고, 에러 코드(2)의 의미는 "file not found"라고 한다.

그런데, 더 구체적인 오류 메시지인 DOS_SetFileAttr()의 메시지는 전혀 출력이 안되었다.
무슨....?
로그 메시지 출력에 어떤 제한이 있는지 다시 확인해 봐야 하겠다.
어차피 실제 속성을 바꾸지 않는 함수인만큼, 오류의 원인을 확인해 보고 별 문제가 아니라면 그냥 성공한 것으로 리턴하게 만들어야 하지 않을까?

나머지는 다음에...

2014년 10월 21일 화요일

Vim 파일 저장 오류 __ 6

open() 함수가 실패했을 때, 어떤 에러가 발생하는지도 확인해 봐야 할 듯해서...

open()의 help
함수 실패시의 리턴값은 -1이고 조금 더 자세한 에러 원인을 알기 위해서는 errno 변수를 확인해 봐야 한다.
errno 값에 대해서 조금 더 확인해 보면,
errno의 help

help화면엔 별것이 없고, ERRNO.H를 열어 보자.
ERRNO.H

그럼, 이제 소스를 고쳐서 errno를 확인해 보기로 하자.

FILEIO.C 메시지에 errno 추가

make

디버그메시지, errno = 19

errno = 19는 EINVAL

결론은 Invalid Argument...휴..

Fileio.c의 mch_open()의 인자들을 모두 확인해 보았고, 두번째 인자들의 flag들도 바꾸어 보았으나 문제가 해결되지 않았다.
비록 에러코드는 Invalid Argument 이지만, 더 low-level에서의 인자를 뜻하는 것이 아닐런지...

인자들만 바꾸어서 문제가 해결된다면, 앞서의 mode와 마찬가지로 UNIX/Windows 차이가 발생할리 없었을 것이다.

지금까지의 시험결과로는 DOSBox 내부에서 문제를 해결하는 것이 불가능해 보인다.
DOSBox와 UNIX 사이의 interface, 특히 파일시스템과 관련된 interface의 문제로 보인다.

문제는 이런 경우의 디버깅이 매우 어렵다는 점....