DLL 이름 바꾸기..

들어가며

DLL이란 게 처음 사람들에게 알려졌을 땐 뭐랄까 응용 프로그램의 일부가 DLL이란 형태로 밖에 나와있는 것이 아닌가 했다. MS-DOS 시절엔 보기 어려웠으니까 Window 시절에 본격적으로 등장한 게 맞다. 그런데 실제로 그러하다. unix의 shared object/shared library의 MS-DOS/Windows 스러운 개념일 뿐이다.

왜 MS는 쓸데없이(?) 동일한 개념의 컴퓨터 요소들을 다른 이름과 형식으로 만들어놓아서 사람들을 이렇게 불편하게 만들어 놓은 것일까, 무슨 음식처럼 골라먹는 맛이 있는 것도 아닌게 되었다는 게 최근의 MS의 움직임을 통해서 확실히 보여지고 있다. 뭐냐고? Windows의 모양을 하고 있지만 unix의 꼴로 가고 있는 것이 그것이다.

powershell이 그러했고 그게 WSL로 갔고, 이미 많은 부분이 unix 세계의 모양새로 수렴하고 있는 상황이다. 그러다보니 예전 시절의 잔재들이 점점 골치거리가 되고 있다. 물론 수많은 사람들의 노력에 의해서 이 세계가 결국엔 하나로 통합되겠구나 하는 변화는 더 빠르게 이루어지고 있지만.

이런 개념으로 이 세상을 보자면 크게 3개의 계로 나눠지는 것 같다.

1) unix 계: linux 2) MS 계: windows/MS-DOS 3) 중간계: mingw 계열 4) *외 계: macOS (dynlib), … 5) *이도저도 아닌 계: cygwin, … 이것은 솔직히 왜 있나 싶다.

DLL 이름 바꾸기

본론으로 돌아와서 DLL 이름 바꾸기가 왜 문제가 되는지 생각해보자.

DLL은 다른 응용프로그램/라이브러리가 의존하고 있는 라이브러리라서 이름을 바꾸게 되면 그것을 불러 쓰는 이들이 파일을 찾을 수 없으니 큰 문제가 생기게 된다. 결국 이름을 바꾸게 되면 그것을 불러다 쓰는 응용프로그램/라이브러리도 거기에 맞춰 다시 빌드해야 한다.

여기까지 생각하면 이 문제는 아주 간단한 것처럼 보일 수 있다.

‘아 그냥 그 라이브러리를 불러다 쓰는 애들을 해당 동적 라이브러리의 파일 이름을 바꾼 다음 다시 빌드하면 되는 거겠네..’

아무리 쉬워보이는 세상의 문제도 막상 마주하면 별로 쉽지 않다는 것을 깨닫게 되는 순간이다.

문제의 시작

문제는 이름을 바꿀 라이브러리는 그냥 DLL 혹은 so의 형태로 존재하고 있고, 그 스스로는 자신의 이름이 다른 것으로 바뀔 것이라는 것은 전혀 모르는 상태로 있다는 것이다. 라이브러리에 따라서 어떤 것은 자기 자신의 이름을 내부적으로 갖고 있는 것들도 있다.

그러니까, 이렇게 되고 보면 스스로의 이름을 바꾸려면 그냥 file name을 rename하는 것으로 끝나는 게 아니라 이름을 바꾼 죄로 아예 그 라이브러리도 rebuild를 해야하는 것이 아닌가 하게 된다.

또는, 글자수가 같은 다른 이름으로 바꾸고 라이브러리 파일 안의 문자열을 그에 맞춰 바꿔야 되는 게 아닐까 한다. 이렇게 해도 되긴 된다. 문제는 글자수가 같아야만 가능하다는 조건이 붙지만.

컴파일러에 따라서 어떤 것은 dll이나 so를 그대로 라이브러리로 받는 것들이 있는가 하면 어떤 것은 import library라고 해서 dll의 entry point를 테이블로 가지고 있는 library이 있어야만 빌드를 할 수 있는 경우도 있다.

무슨 말이냐고?

그러니까 빌드할 때

1) unix 세계에서는 shared object를 곧바로 link할 수 있다. 2) MS 세계에서는 DLL을 축약한 import library를 link해야 한다. 3) unix와 MS의 중간계 (mingw계통)에서는 import library 또는 DLL을 link할 수 있다.

shared object의 경우

shared object (so, unix 계열의 동적라이브러리)의 경우는 별도 import library를 가지고 있는 것이 아니니까 파일 이름을 바꾼 뒤에 그것을 참조하는 응용프로그램이나 라이브러리를 다시 빌드하면 된다…라고 하면 문제가 너무 쉽게 해결된다.

그런데 이미 말했던 바와 같이 shared object는 자신의 파일 이름을 내부적으로 가지고 있다. 이것을 soname이라고 한다. 따라서 파일 이름을 바꾸고 그것을 불러 들이는 애들을 다시 빌드해도 다시 빌드된 아이는 soname을 참조해서 예전 이름의 동적 라이브러리를 부르게 된다.

그러면 어떻게 해야되나?

patchelf라는 응용프로그램이 있다. 없다면 간단히 설치하면 된다. 옵션을 뭘로 줘야 되냐고? 그걸 누가 다 기억하나? help는 이때 쓰라고 있는 것이다.

shared object의 경우 해결방법이 너무 간단해서 차라리 고맙다고 할 수 있다. 그래서 이글의 제목에 일반적인 동적 라이브러리나 shared object가 아닌 DLL이 쓰인 것이다.

dll의 경우

dll의 경우 그것이 MS의 build tool로 만들어진 것이냐 아니냐에 따라 방법이 달라진다. 왜 똑같은 dll인데 MS와 비 MS가 차이가 있냐? 라고 물어볼 수 있는데, 낸들 아나? 빌드까진 어떻게 어떻게 되지만 제대로 돌지 않으니 안된다 할 뿐이다.

어차피 MS가 비 MS 계통을 인식했을리 없고, 비 MS 계통은 unix 계통의 것을 MS의 것처럼 옮겨놓은 것이니까 둘이 완벽히 통일 될 수가 없었던 것이었겠거니 할 뿐이다.

MS 계통 DLL

MS 계통의 DLL은 관련 툴이 MSVC와 함께 따라온다.

dumpbin 이란 것과 lib라는 툴 인데, dumpbin이라는 것은 DLL의 내용물을 까보는 것이고 lib라는 툴은 import library를 만들어내는 툴이다.

쉽게 말해서 dumpbin이라는 것은 linux 세계의 objdump이고 lib라는 툴은 자기들이 import library를 써야되니 겸사겸사해서 만든 게 아닐까 하는 추측이다.

그래서 어떻게 하느냐?

1) dumpbin으로 DLL의 내부를 축약한 내용을 먼저 뽑는다. DLL에 어떤 심볼들이 들어있는지 그 목록을 뽑아내는 거라고 보면 된다. 2) 다음과 같은 형식으로 def 파일을 만든다. def file은 import library의 밑그림이라고 볼 수 있다. 간단하게 이런 형식이다.

LIBRARY   BTREE
EXPORTS
   Insert   @1
   Delete   @2
   Member   @3
   Min   @4

여기서 LIBRARY에 해당하는 부분만 수정한다. 뭘로? 내가 바꾼 dll의 이름으로 수정하는 것이다. 그리고 나머지 부분은 dumpbin에서 뽑아낸 것들로 채워넣는다.

어떻게? regex 같은 거 써서 적당히 형식 변경해주는 거다. MS 세계에서는 이게 잘 안되니까 WSL이나 docker로 하는 게 편리하다. batch도 스스로 파싱하는 기능이 있긴 하니까 할 수 있긴 하다.

3) lib라는 툴을 이용해서 위에서 만들어진 def file을 가지고 import library를 만들어낸다.

자세한 것은 구글링을 참조하길 바란다.

중간계 DLL

이게 가장 문제가 되는 경우라고 볼 수 있다. 같은 DLL이니까 위의 방법대로 하면 되는거 아니냐고 할 수 있다. 될 것 같은데 안된다.

왜? 나도 모른다.

중간계 개발툴을 잘 들여다보면 생전 처음 보는 유틸들이 있다.

1) gendef 2) dlltools

얘네 둘로 문제를 해결할 수 있다.

1) gendef는 dll에서 심볼들을 뽑아내서 def file을 만들어낸다. 2) def file의 LIBRARY 필드를 변경한 dll 파일의 이름으로 바꿔넣는다. 3) dlltools를 이용해서 def file + dll file을 가지고 import library를 만들어낸다.

얘들은 linux 세계에도 있고 MS 세게에도 있고 중간계에도 있다. 어떤 계에 있는 애들이 좋냐고? 개발툴은 무조건 linux계에 있는 애들이 좋다고 말하고 싶다. 빠르고 버전업도 잘되고.

여기서, 왜 중간계의 툴이 중간계에서 가장 좋지 않고 linux계에서 가장 좋냐라고 물어볼 수 있다. 중간계는 사실상 몸뚱이는 linux계인데 껍데기만 MS계이기 때문에 숙명적으로 그럴 수 밖에 없다. WSL이 보편화되고 나선 사실상 중간계 이거 왜 있나 싶을 정도인데, WSL의 W도 모르는 사람들이 세상에 훨씬 더 많고 중간계가 있는 것이 더 몸집이 작고 날렵한 경우도 있기 때문에 어쩔 수 없는 선택이라고 본다.

이렇게 만든 import library를 쓰면 중간계 DLL의 이름 변경 후 그것들을 불러다 쓰는 이들의 리빌드가 가능해진다.

흔히 실수하는 문제

링커에 argument를 붙일 때, 그 순서가 매우 중요하다. 사실 이것을 자주 잊는다. 수많은 object를 linker에 나열할 때 (내가 나열하지 않고 make가 해주지만) 앞쪽의 dependency를 보상해주는 object가 반드시 뒤에 와서 붙어야 한다. 이 순서가 뒤바뀌면 내가 100을 다 잘 했더라도 문제가 생긴다.

또 여기서 생기는 문제가 이런 사소한 문제로 일이 진행되지 않으면 다른 쪽에 문제가 있나 싶어서 모든 곳을 다 건드리게 된다는 것이다. 기본기가 이래서 중요하다 라고 할 수 있을 수도 있지만, 사람이란 게 늘상 이것만 하고 사는 것도 아니고 이거 저거 하다보면 기본기가 아무리 잘 다져져 있다해도 잊게 마련이다. 그래서 별 것 아닌 것 같아도 여기 저기 적어놓으면 언젠간 도움을 받을 때가 있다. 사실 그래서 이것을 적어놓는 것이다.

이걸 왜 내가 알아냐 하나?

재미다. 그냥 뭔가 일을 하다가 안풀리는 문제가 있으면 ‘불가능하다’ 보단 ‘해결방법이 있다’하는 게 더 즐겁지 않은가?

아주 쉬운 예로 어떤 좋은 응응 프로그램의 DLL의 기능 일부를 쓰고 싶을 때가 있다면 이것 방법을 쓰는 것도 좋은 방법이다. 물론 온전하게 사용하려면 API를 알고 있어야 한다. 그런데 인터넷 세상에서 웬만한 것들은 다 찾으면 나오게 되어있다.

얼마나 좋나? 귀찮은 것 내가 다 만들지 않아도 누군가 잘 만들어 놓은 것들 API만 알고 있으면 그대로 써먹을 수 있는 세상이다. 그래서 know how보단 know where가 낫다는 소리들 하지 않는가? 문제는 know where 다음에 know which (one is better)도 알만한 입장이 되어야 한다는 것이겠지.