Wine은 어떻게 동작하는가 101
(werat.dev)- Wine은 POSIX 호환 OS(리눅스,macOS,BSD)에서 Windows 프로그램을 실행가능하게 해주는 호환레이어
- 밸브의 Steam Deck도 Wine기반 솔루션을 사용
WINE = Wine Is Not an Emulator
- 에뮬레이터 방식은 느리고, 실제로 Linux/macOS는 네이티브하게 윈도우 바이너리를 실행가능함 (조금만 도와준다면)
- 디버거를 통해서 리눅스/윈도우 바이너리가 어떻게 동작하는지 상세 설명
Hello, Wine!
- 기본적으로 Wine은 윈도우 실행파일에 대한 "Dynamic Loader"임
- 네이티브 리눅스 바이너리이고, EXE나 DLL을 어떻게 처리해야 하는지 알고 있음
- Wine은 윈도우 실행파일을 메모리에 읽어들인 후, 파싱해서 의존성을 파악하고, 실행해야할 코드로 점프
- 이것만으로도 일단 윈도우 바이너리를 실행가능하지만, 예외가 있음
System Calls
- syscalls 라고 부르는 시스템호출이 Wine을 복잡하게 함
- syscalls 는 OS에 구현되는 것이며, 실행파일 또는 라이브러리에 들어있는게 아님
- OS에서 제공하는 syscalls는 OS API
- 리눅스 : read, write, open, brk, getpid,..
- 윈도우 : NtReadFile, NtCreateProcess, NtCreateMutant,..
- 시스템 호출은 코드의 일반 함수호출과 다름. 예를 들어, 파일 오픈 같은 것은 File Descriptor를 추적하기 때문에 커널에서 처리되어야 함
- 따라서 응용 프로그램 코드는 자체적으로 "인터럽트"하고 커널에 제어를 제공하는 방법이 필요("Context Switch")
- OS가 제공하는 함수 집합과 호출 방식은 OS별로 다름
- 리눅스에서는 read() 함수를 호출하면 파일 디스크립터를 레지스터 %rdi, 버퍼 포인터를 %rsi, 읽을 바이트수를 %rdx 에 넣어야 함
- 하지만 윈도우는 read() 함수가 커널에 없음
- 같은 "Hello World!" 를 인쇄하는 코드를 리눅스/윈도우에서 실행해보면
- 리눅스는 libc.so 의 puts를, 윈도우는 ucrtbase.dll 의 printf 를 호출
- 요즘 리눅스에선 정적으로 링크해버려서 puts 구현체를 바이너리안에 포함시켜서 libc.so가 실행중에 안쓰이는게 일반적
- 윈도우에서는 적어도 최근까지만 해도 "Malware만 시스템 호출을 직접 사용했음"
- 일반적인 응용프로그램은 항상 kernel32.dll/kernelbase.dll/ntdll.dll 에 의존하여 커널과 직접 통신하지 않게 함
Runtime translation of Syscalls
- syscall 을 인터셉트하면 어떨까?
어플리케이션이 NtWriteFile()을 호출할 때 끼어들어서 write()를 호출한 후 바이너리가 원하는 결과 포맷으로 리턴해 준다면? - 커스텀 버전의 ucrtbase.dll 을 제공한다면 가능하겠지만, 복잡한 문제가 발생
- 대신에 바이너리와 커널사이에 끼어있는 ntdll.dll을 수정
- 최근 버전의 Wine은 ntdll.dll(PE 바이너리) 과 ntdll.so(ELF 바이너리) 로 구성
- dll은 얇은 레이어로 단순히 콜을 ELF쪽으로 리디렉션함
- ELF는 __wine_syscall_dispatcher 라는 특별한 함수를 가지고 있어서, 현재 스택을 윈도우에서 리눅스, 또는 그 반대로 변환하는 마법을 부림
- 이 syscall dispatcher 가 윈도우 세계와 리눅스 세계를 연결하는 다리임
- 콜링 컨벤션을 처리하고, 스택 공간을 할당하고, 레지스터를 옮기고 하는 등의 처리를 함
- 실행이 ntdll.so로 와서 리눅스 바이너리로 넘어오면, 우리는 모든 Linux API를 사용할 수 있게 됨
이게 다라고?
- 매우 쉬운 것처럼 들리지만..
- 윈도우 API는 엄청 많고, 문서화가 잘 되어있지 않으며, 알려진/안알려진 버그들이 있고, 그것들은 있는 그대로 보존해야함. 대부분의 Wine 코드는 다양한 윈도우 DLL의 구현체임
- syscall 호출엔 다양한 방법이 있고, 기술적으로 어플리케이션들이 syscall을 직접 호출하는 것을 막을 방법이 없음
(윈도우 게임들은 모든 미친 짓을 한다는 것을 기억할 것)
리눅스 커널은 이걸 처리하는 특별한 메커니즘이 있고, 물론 이게 복잡도를 가중 시킴 - 32bit vs 64bit 문제도 넌센스임. 수많은 32비트 게임들이 있고, 그들은 다시 64비트로 재릴리즈 되지 않을 것. Wine은 둘 다를 지원하기 때문에 이 역시 복잡도를 가중 시킴
- 여기서 wine-server는 거론도 하지 않았음. 이것은 Wine이 생성하는 별도의 프로세스로, 커널의 "상태"(파일 디스크립터, 뮤텍스 등)을 유지함
- 게임을 실행하고 싶다면? DirectX 와 PulseAudio, 입력 디바이스 등을 처리해야해서 일이 엄청 많음
- 와인은 오래 개발되어 먼 길을 왔음. 오늘날엔 최신 게임인 Cyberpunk 2077 이나 Elden Ring을 아무 문제없이 실행 가능
심지어 가끔은 Wine이 Windows보다 더 나은 성능을 보일 때도 있음
구독형 독서 서비스를 제공하는 yes24 와 교보문고를 사용 중입니다.
집 PC 환경을 우분투로 바꾸고 나서 와인을 이용해 YES24 와 교보문고를 실행해보았는데
둘 다 별도의 DRM을 사용하고 있어 실행이 될까했지만 QT로 만든것으로 알고있는 YES24는 실행이 잘 되었고 교보문고 EBOOK은 실행이 안되더라구요. (UI 실행 , DRM 실행 X)
둘 다 DRM 적용된 것으로 아는데 어떤 점에서 실행이 되고 안될까 생각을 해보았지만 위 글을 보니 대충 이해가 갈 듯 하네요(대충 완벽히 이해했어 짤)
wine5.0 이후로 카카오톡이 아무런 설정없이 실행되서 행복합니다. (카카오톡을 쓰고싶지는 않은것과 별개로..)
몇가지 화면표시에 문제가 좀 있지만, 클립보드 이미지 전송등의 기능이 심리스하게 동작합니다.
대체로 와인이 게임실행에 집중하고 있는 것 같은데, 다양한 앱들도 잘 실행시켜주니 좋아요.
공공기관 리눅스 도입 얘기가 나와도 리눅스버전 카카오톡은 생각도 하지않는 것은 좀.. 많이 별로긴함..
맥버전은 뚝딱 만들어내더니..
대체로 와인이 윈도우 프로그램을 잘 실행시켜주기 때문에, 와인을 이용해서 크로스플랫폼 앱을 만든다! 라는 구상도 가능할까요?(데스크탑 한정)
아주 옛날에는 한컴의 HWP도 wine 기반으로 리눅스용이 포팅되어서 발매된 적이 있었다고 알고 있습니다. (R4는 별도의 win32호환 라이브러리 레이어가 들어갔었고, wine이 사용된건 R5인지 2002인지 가물가물 하네요)
그래서, 한때는 wine덕분에 win32가 가장 대중적이고 성공적인 크로스플랫폼 API라는 드립도 있었습니다.
하지만 이제는 electron/wasm 의 시대;;
조금 다른 이야기지만, 만약 그렇게 하시려면 -- Wine의 라이선스가 LGPL이기 때문에, 코드 작성 방법에 따라 소스코드의 일부 또는 전체를 공개해야 할 수 있습니다.
원문에도 설명되어 있지만, Wine이 에뮬레이터가 아닌 이유는 CPU 명령어를 그대로 쓰기 때문입니다. 그 말은, Wine으로 돌릴 수 있는 소프트웨어는 기본적으로 x86 혹은 x86-64 CPU에서 동작하는 윈도우용 소프트웨어라는 의미입니다.
애플은 Mac 전체를 ARM 아키텍쳐로 이전한 데다가 MS에서도 ARM 기반 개발 키트를 내놓고 있는 현 시점에서, x86(-64) 기반 CPU에서만 동작하는 소프트웨어를 “크로스플랫폼 지원”이라고 하기에는 좀 무리가 있지 않을까 합니다.
electron이나 tauri가 있기 때문에 크로스플랫폼을 처음부터 만들어야 한다면 좋은 선택은 아닌듯 하네요.
웹 브라우저 기반의 기술을 쓰면 안되는 특별한 제약이 있다면
크로스 컴파일링을 잘 지원하는 Qt같은 라이브러리의 사용이 더 좋을지도요..