2013년 12월 19일 목요일

DOSBox의 커맨드 쉘(Command Shell) 버그 ___ 2

먼저 간단하게 정리한 GDB의 명령어들.




여기에 정리한 명령어 외에,
(gdb) until <line>
: <line>까지 실행
(gdb) return
: 현재 함수 벗어나기

이 정도의 명령어만 알고 있어도 디버깅이 가능했다.
(이번 디버깅에선 register, memory dump, call stack에 대한 접근이 불필요했기에...)
단, GDB에서의 source listing 기능은 아무래도 많이 부족하므로 별도의 소스 뷰어와 사용하면 좋을 듯 하다.


이제, 본격적으로 디버깅!

DOSBox를 타깃으로 gdb를 실행하고 바로 run.

DOSBox가 시작된 후에, 문제를 확인하기 위해 D drive로 바꾸어 둔다.

CTRL-C를 눌러서 잠시 중단시킨다.
break로 DOS_Shell::Execute에 브레이크포인트를 설정하고, c로 다시 실행을 재개한다.

문제의 "nc"를 입력한다.

앞선 포스팅에서 문제가 되는 부분의 함수 호출 순서를 정리했다.
다시 보면,

shell/shell.cpp 286 DOS_Shell::Run()
shell/shell.cpp 295 temp.ParseLine()
shell/shell.cpp 199 DOS_Shell::ParseLine()
shell/shell.cpp 251 DoCommand()
shell/shell_cmds.cpp 118 DOS_Shell::DoCommand()
shell/shell_cmd.cpp 153 if(Execute())
shell/shell_misc.cpp 358 DOS_Shell::Execute()
shell/shell_misc.cpp 387 Which()
shell/shell_misc.cpp 511 DOS_Shell::Which()
shell/shell_misc.cpp 572 if(DOS_FileExists())...

이 가운데 DOS_Shell::Execute()에 브레이크포인트를 설정해 둔 상태였고,
"nc"를 입력하여 이 브레이크포인트에서 멈추기를 기대하였다.


DOS_Shell::Execute에서 멈추었고, 이후의 소스를 확인한다.

중간과정은 관심밖이므로, 원하는 387 line Which()까지 실행하도록 u 387을 입력한다.

Which() 내부의 처리를 보기 위해 s(tep)을 입력하고 Which의 처리과정을 확인한다.
현재 위치(D:\)에서 확장자(.com, .exe, .bat)를 붙여가며 파일이 존재하는지 확인한다.
당연히 없으므로 Which() 함수에서의 처리가 계속된다.

이제는 PATH를 하나씩 순서대로 뒤져가며 파일을 찾는다.
중간에 확인하고 싶은 변수는 p(rint) 명령을 사용한다.

PATH 환경변수에서 하나의 경로만을 떼어내는 과정이었다.

첫번째 PATH인 Z:\에 대해서 입력된 nc를 붙여 다시 파일을 찾는다.

다음인 C:\FREEDOS도 실패하고,
그 다음인 C:\에 대해서는 "u 571"이 이전과는 달리 571 line에서 멈추지 않았다.

C:\NC가 아닌 C:\에서 "nc"라는 파일을 찾았다고 판단하게 된 것이 문제의 원인이었다.

Which() 함수는 대체로 다음의 순서대로 파일을 찾는다.

1) 현재 위치에서 "nc"
2) 현재 위치에서 "nc.COM"
3) 현재 위치에서 "nc.EXE"
4) 현재 위치에서 "nc.BAT"
5) PATH에 있는 각 경로에 대해서 위의 1) ~ 4)를 반복 검색

문제가 된 것은 "C:\" 경로에서 "nc"를 찾는 것이 성공했기 때문이다.
어째서 일까?
DOS_FileExists()는 다음과 같은 순서로 진행이 된다.

dos/dos_files.cpp 1176 DOS_FileExists()
dos/dos_files.cpp 1179 return Drives[drive]->FileExists(fullname);
dos/drive_local.cpp 374 localDrive::FileExists()
dos/drive_local.cpp 380 FILE* Temp=fopen(newname,"rb");

최종적으로 불리는 fopen() 함수의 인자로 주어진 경로는 파일이 아닌 폴더였지만, 폴더에 대해서도 fopen()이 성공한 것으로 처리했던 것이다.

nc와 pe2만이 문제가 되었던 것은, 서브디렉토리의 이름과 실행파일의 이름이 동일했기 때문이다.

자, 이제 문제의 원인이 밝혀졌으니 이를 해결할 방안은 세워야겠다.
1) Path와 실행파일의 이름이 같지 않도록 만든다.
2) 가장 depth가 큰 path가 앞에 오도록 path의 순서를 바꾼다. ( C:\;C:\NC -> C:\NC;C:\ )
3) Source 수정 : File인지 디렉토리인지 구분하여, 파일인 경우에만 존재여부를 확인 할 수 있도록 한다.
4) Source 수정 : File의 존재 여부를 확인할 때, 확장자가 없는 경우에는 확장자를 먼저 붙인 다음에 존재여부를 확인하도록 한다.

1)과 2)의 방법은 실제로 효과가 있음을 확인하였다.
하지만 3)과 4)의 방법은 시도도 하지 못했다.

디버깅을 하면서 느낀 점은, Which() 혹은 ParseLine()이라는 부분의 처리가 매우 "땜질"식으로 짜여져 있는 느낌을 받았기 때문이다.
비효율적인 부분(PATH를 매번 분리하는 작업), 중복처리(확장자를 붙여서 찾는 부분이 중복되어 "nc.COM.COM"을 찾기도 한다.) 등의 문제가 있는 것으로 미루어보아, 여러 문제가 발생해서 수정이 여러번 이루어졌으며 그나마도 한사람이 아닌 여러 사람이 고친 것으로 보였기 때문이다.
만약 내가 또 하나의 수정을 가하면, 점점 걸레화 되어갈 것이 뻔하다.
그리고 내가 모른 문제가 다시 튀어나올지도...

따라서 1)과 2)의 방법으로 문제를 해결하길 권한다.


마지막으로 이 문제가 어째서 M$-Window$에서는 발생하지 않는지 확인해 보았다.

직접 디버깅해 본 결과 M$-Window$에서는 fopen()의 인자로 폴더가 주어졌을 때, 실패한 것으로 처리가 됨을 확인했다.

댓글 없음: