레이블이 버그인 게시물을 표시합니다. 모든 게시물 표시
레이블이 버그인 게시물을 표시합니다. 모든 게시물 표시

2016년 3월 21일 월요일

파이썬의 라이브러리 NumPy + SciPy _____ 5

==========================================================
이 포스팅은 쓸모 없어진 포스팅입니다.
(NumPy + SciPy ____ 4 ~ NumPy + SciPy ____ 8)
해당 문제점은 Scipy 패키지를 빌드했던 툴(아마도 MS Visual Studio 201x)이 Windows XP를 지원하지 않아서 발생한 것으로 추측하고 있습니다.
이후의 포스팅에서 이 시행착오를 정리할 계획입니다. (아마도 NumPy + SciPy ____ 9)
==========================================================

정말 정말 궁금했습니다.
왜 나만 안된다고 혼자서 이리도 고생을 하는건지....
정말 미치도록 알고 싶었습니다.

그래서 결국....파이썬의 소스를 보기로 했습니다.

마이크로 소프트가 언급한 DLL의 탐색 경로에 들어가 있음에도 자꾸 DLL을 찾지 못한다고 나오는게 너무 답답해서 누가 잘못하고 있는건지 알아봐야겠습니다.

이젠 귀찮아서 사진도 어지간하면 안올릴 생각이다.

다운로드 받은 소스는 2.7.11
https://www.python.org/downloads/release/python-2711/


소스 분석엔 소스인사이트가 최고.

핵심만 빠르게 찾아가기 위해서 메시지를 바로 찾아 보는게 장땡.

"DLL load failed"라는 문자열로 검색한 결과

dynload_os2.c
dynload_win.c

위의 두개 파일만 검색 된다...그나마 다행.
이름을 보니 볼 거 없이 dynload_win.c가 내가 찾는 파일임이 확실하다.

dynload_win.c

DLL load failed는 저기 보이는 소스에서 조금 더 내려가면 나온다.

저기서 봐야 할 것은 GetFullPathName()과 LoadLibraryEx().

먼저 해당 함수에 대한 MSDN의 설명을 참조.

GetFullPathName()

첫번째 두번째 인자가 입력이고 세번째 네번째가 출력.
그러니까 pathname을 넣고 pathbuf를 받아온 것이다.

그러면 다음엔 pathname이 아닌 pathbuf를 사용해야 할텐데, 다음의 LoadLibraryEx()에서는 pathname을 사용하고 있다.???

LoadLibraryEx()에 대한 설명도 찾아 보자.


LoadLibraryEx()

이 함수는 좀 복잡한데 마지막 인자인 dwFlags에 따라서 DLL 탐색의 순서나 방법이 달라진다고 한다.

LoadLibraryEx()의 dwFlags 설명 중.

이 플래그에 대한 설명도 참 복잡한데, LOAD_WITH_ALTERED_SEARCH_PATH를 사용할 때에는 반드시 파일을 절대 경로로 적어야 한다는 것.
그리고 탐색방법은 아래에 설명....한다고 되어 있는데, 설명이 없다.휴....

다른 페이지에서 설명을 찾았는데,

Dynamic-Link Library Search Order이라는 페이지이고,

여기에서
Alternate Search Order for Desktop Applications
이라는 항목을 찾아보면 설명이 나와 있다.
하지만 해당 항목을 읽어보면 알겠지만 통상적으로 탐색을하는 디렉토리의 순서가 다를 뿐 크게 문제가 되지는 않아보인다.

그렇다면 문제가 될 것으로 예상되는 건, 앞서 LoadLibraryEx()에서 LOAD_WITH_ALTERED_SEARCH_PATH 플래그 사용시 반드시 절대경로를 사용해야 한다는 문구였다. 절대 경로가 아닐 경우에는 결과가 어찌될지 모른다는게 설명...


과연 파이썬 소스에 결정적인 문제가 있어서 이것이 원인이었는지를 확인하려면,

1. 직접 파이썬을 빌드하고 해당 부분을 적절하게 수정한 뒤 문제가 해결되는지 확인한다.

2. 파이썬 3.x의 경우에는 이 부분이 같은지 다른지 확인하고, 또 문제가 발생하는지 여부를 확인해 본다.
  2-1. 파이썬 3.x도 소스가 같고 문제도 동일하게 발생한다면 위의 1번 방법으로 확인해야 한다.
  2-2. 파이썬 3.x도 소스가 같은데 문제가 발생하지 않는다면, 문제의 원인은 다른 곳에 있다는 의미이다. 골치 아픈 케이스.
  2-3. 파이썬 3.x는 소스가 다르고 문제가 발생하지 않는다면 3.x의 소스를 최대한 활용해서 무엇이 문제였는지 최대한 유추해 볼 수 있다. 하지만 결국엔 1번의 과정을 밟아야 한다.
  2-4. 파이썬 3.x는 소스가 다른데 문제가 동일하게 발생한다면 완전히 별개의 문제로서 따로 따로 소스를 분석해야 하지만, 파이썬 소스의 문제가 아니라 내가 사용하는 환경의 문제일 가능성이 높아진다.

3. 최소한의 비용으로 최대한의 결과를 얻기위해 리버싱으로 살짝만 확인해본다.(?)


이상의 계획을 가지고 생각해보니, 먼저 2번의 과정을 거치는게 빠르겠다 싶었다.

그래서 파이썬 3.x의 소스를 받아서 확인해 본 결과....소스가 달랐다.

아예 LoadLibraryEx()를 부르는 함수에 들어오기 전에 먼저 처리를 한게 아닐까 싶게...그래서 소스만으로 비교를 하기엔 무리가 있어서 직접 실행해 보기로 했다.

그.런.데......파이썬 3.x는 Windows Vista이상에서만 돌아간단다.....설치도 안된다...ㅠ.ㅠ

결국 2번의 방법은 몽땅 수포로 돌아가고....

1번의 정공법이냐, 3번의 편법이냐....만 남은 상황이다.

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월 18일 토요일

Vim 파일 저장 오류 __ 4

일단 터보 디버거는 미뤄두고, 간단히 메시지로 디버깅...

메시지 출력에 원하는 정보를 넣어서 출력하게 하려면 소스를 조금 수정해야 한다.

일단 메시지용 변수를 추가하고, 출력할 메시지를 만드는 부분을 추가해야 한다.

메시지 출력용 변수 추가 (#2781)

출력할 메시지 만들기 (#3970,#3971)

소스의 수정은 Ubuntu 상에서 했으므로, DOSBox에서는 파일이 변경된 것으로 인식할지 아닐지 몰라서 touch 한번 해주고 make를 해 보았다.
빌드 성공

우선 newfile 이라는 이름으로 새 파일을 만들어 본다.
새 파일 만들기

새 파일 만들기는 이전에 그랬듯이 성공.
저장 성공

이번에는 수정하기.
수정 실패, 메시지 확인.
역시나 실패. 메시지는 원했던 대로 출력되었다.
변수 값은 append = 0, forceit = 0, perm = 32

이 값들의 의미를 확인하기 위해 다시 소스를 보자.

append가 0인 것은 약간 의외....이지만 큰 영향은 아닌 듯 보임.
forceit은 당연히 0일 것이다.(":w!"가 아닌 ":w"였으니까..)
그런데 perm은 32? 8진수로 바꾸면 040?
UNIX 파일시스템이라면 소유자의 권한은 없고, 그룹은 쓰기 권한만 있는 셈?
하지만, 이건 MS-DOS의 FAT 파일시스템이므로 무슨 의미인지 정확하지가 않다.
그런데도 소스에서는 0666, 0777과 같이 UNIX 파일 시스템에서의 권한과 동일하게 다루고 있는 모습은 의문이다.

우선 perm의 값이 어떻게 32가 나온것인지 확인해 보기 위해 소스를 따라가 보자.
위의 mch_open() 이전에 perm 값을 구해 오는 부분은 다음의 mch_getperm() 부분이다.

perm을 구하는 부분
 mch_getperm()은 다시 vim_chmod()를 호출한다.
mch_getperm()
 vim_chmo()에서는 _chmod() 함수를 호출한다.
vim_chmod()
_chmod()는 UNIX의 chmod() 함수와는 다른 것으로 보인다.(인자의 갯수도 다름)

Borland C 매뉴얼을 찾아 봐야할 듯...

2014년 10월 17일 금요일

Vim 파일 저장 오류 __ 3

먼저 Vim의 소스를 직접 빌드하여 디버깅 해 보기로 결정.

vim의 소스는 다음 링크에서...
ftp://ftp.vim.org/pub/vim/pc

MS-DOS의 경우에는 vim 7.1이 최종 소스이므로 vim71src.zip을 다운로드 하면 된다.

소스를 풀어서 src 디렉토리를 살펴보면 INSTALLpc.txt라는 파일에 소스 빌드에 관한 안내가 되어 있다.

그 가운데 MS-DOS에 관한 부분.
INSTALLpc.txt
네모친 부분을 읽어 보면 Borland C++을 사용하는게 제일 속편할 듯...

그리고 부가적으로 spawno라는 라이브러리가 필요하다고 한다.
위에서는 simtel에서 다운로드 하라고 하는데, 이걸 만든 저자 Ralf Brown의 사이트를 본 적이 있어서 그곳에서 다운로드 받았다.

http://www.cs.cmu.edu/~ralf/files.html (예전에 MS-DOS 인터럽트를 정리해 둔 곳이라고 했던 URL. 이 페이지를 살펴보면 spawno 라이브러리를 찾을 수 있다.)
http://www.cs.cmu.edu/~ralf/pub-files/spwno413.zip (spawno 라이브러리)

project라는 서브 디렉토리 아래에 모두 풀어 놓았다.
디렉토리 모습
vim과 spawno 트리

소스를 빌드하기 위한 작업은,
1. 당연한 사항이지만 Borland C++을 설치해서 path 지정해 두기
2. src/Make_bc3.mak를 Makefile로 복사(또는 이름 바꾸기)
3. Makefile 수정하기
Makefile 수정
4. make

빌드는 성공적으로 완료.
디버깅을 위해 터보디버거를 이용해 구동.
하지만 ...메모리 부족 에러 발생ㅠㅠ
터보디버거 에러

고민하며 둘러보다 Makefile을 추가적으로 수정해야 함을 알게 됨.
주석에 씌어진 대로 80386 코드로 생성하도록 "-1-"을 "-3"으로, 소스 디버깅을 위해서 "-v" 옵션을 추가.
Makefile 추가 수정

하지만 결과는 마찬가지...

이제 해 볼 수 있는 방법은,
td386.exe라는 386용 터보디버거를 사용하는 방법과
소스에 메시지를 넣어서 메시지로 디버깅하는 방법.
전자는 터보디버거에 대한 매뉴얼을 좀 읽어 봐야 할 수 있을 것 같으니 후자를 먼저 해 보는 것이 좋을 듯...

Vim 파일 저장 오류 __ 2

문제의 에러코드를 이용해 소스 확인 fileio.c

오류 메시지의 해당 소스

mch_open()이라는 함수의 실패로 인한 오류 메시지였음.

mch_open()은 open() 함수

open()의 man page

open()의 man page, mode와 관련된 부분.

DosBox에서 Vim으로 새로운 파일을 만들어 본다.

내용을 입력하고 저장

정상적으로 저장이 되었음

Ubuntu의 file system에서 확인해 보면 파일의 권한이 644임을 알 수 있음

Vim에서는 수정이 안되지만, edit라는 다른 프로그램으로 수정을 함

정상적으로 수정이 되었음을 알 수 있다

어째서 vim 계열의 프로그램들만 파일의 수정에 문제가 생기는 것일까?
(vim외에도 elvis라는 프로그램도 문제가 발생한다.)
어째서 다른 에디터로는 문제가 없는 것일까?

이 문제의 원인을 어떻게 찾아낼 것인가?
MS-DOS 상에서 소스를 빌드하고 디버깅을 해야 할까?
DOSBox를 디버그 모드로 실행하고 MS-DOS Interrupt를 break point로 해서 확인을 해야 할까?

이 문제의 원인을 찾아내서 문제의 원인이 DOSBox인 경우, vim인 경우에 각각 어떻게 할 것인가?
아니다 이 문제는 DOSBox 뿐 아니라 Virtual Box의 경우에도 발생하므로 vim의 문제로 보는 것이 맞을 것이다.
단지, vim에서 FAT을 기준으로 작동하는 코드, 그리고 Linux상의 MS-DOS 에뮬레이터들이 FAT을 다루는 부분이 서로 충돌해서 빚어진 것이 아닐까 싶다.

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()의 인자로 폴더가 주어졌을 때, 실패한 것으로 처리가 됨을 확인했다.