요약
Eurydice는 Rust 코드를 원래 구조가 살아 있는 C 코드로 옮겨 주는 컴파일러입니다.
LWN이 Rust 컴파일러 구현이 다양해지는 흐름 속에서 Eurydice를 소개했습니다. Rust 정형 검증 도구를 만드는 Aeneas 프로젝트의 일부로, 인리아와 마이크로소프트 소속 개발자들이 2023년부터 만들고 있습니다.
왜 중요한가
- 검증·인증 도구가 C 코드만 받는 고신뢰 소프트웨어 분야에서도 Rust로 짠 코드를 쓸 길이 열립니다.
- C 컴파일러만 있고 Rust 컴파일러는 없는 환경에도 Rust 코드를 옮길 수 있습니다.
- 이미 일부 양자내성 암호(양자 컴퓨터로도 풀기 어렵게 설계한 암호) 코드를 C로 옮기는 데 쓰였습니다.
핵심 내용
- Charon이 rustc에서 MIR(Rust 컴파일러의 중간 표현)을 JSON으로 뽑으면 Eurydice가 이를 KaRaMeL 형식으로 바꿉니다.
- 이어서 Rust 고유 구문을 걷어 내고, F* 언어용으로 쓰던 KaRaMeL 코드 생성기로 C를 만듭니다.
- 결과물은 원래 제어 흐름을 따르며, rustc가 내놓는 얽힌 루프보다 훨씬 읽기 쉽다고 필자는 평가했습니다.
- C에는 제네릭이 없어 타입마다 함수를 따로 만들고, 평가 순서를 지키려고 uu____0 같은 임시 변수를 넣습니다.
- 아직 작은 예제 수준을 넘기 어렵고 const 제네릭 같은 최신 기능에서 자주 실패해, 원본이 계속 바뀌는 코드에 가장 쓸모 있다고 봤습니다.
HN 반응
- 읽을 수 있는 C라면 Rust 컴파일러를 소스부터 다시 빌드하며 숨은 백도어를 감사하기 좋다는 기대가 나왔고, 결과물을 다 검토할 사람은 없다는 반론도 있었습니다.
- 읽기 쉽다면서 반환값을 uu____0 같은 이름에 담느냐는 지적이 있었고, 마이크로소프트와 구글 암호 라이브러리에 적용하는 작업이 진행 중이라는 보충도 나왔습니다.
가독성이 셀링 포인트라면, 조금만 더 신경 써서 이 변수 이름을 하다못해 return_value 같은 걸로 짓는 휴리스틱 정도는 넣어 주면 안 되나요?
AeneasVerif(와 Jonathan Protzenko)의 다른 프로젝트도 꼭 살펴보시길 강력히 추천합니다.
Scylla는 Eurydice의 쌍둥이 격이고, Charon과 이름이 같은 Aeneas도 있습니다.
그리고 "마이크로소프트와 구글의 암호 라이브러리에 각각 Eurydice가 생성한 코드를 통합하는 작업이 진행 중입니다." [1] 라는 대목이 정말 멋지다고 생각합니다. 이 링크는 Eurydice를 소개하는 글로도 훌륭합니다.
[1] https://jonathan.protzenko.fr/2025/10/28/eurydice.html
읽기 쉬운 C가 나온다면 러스트 컴파일러를 소스부터 완전히 부트스트랩하는 데 아주 좋겠네요.
부트스트랩에 쓰려고 하는데 왜 읽기 쉬워야 하죠?
신뢰 모델에 따라 완전히 달라집니다. C 컴파일러만 있는 새 플랫폼에서 러스트 컴파일러를 빌드할 수 있기만 하면 된다면 신경 쓸 필요가 없습니다. 하지만 톰슨식 트로이 목마가 걱정된다면 부트스트랩 체인에 있는 모든 소스를 감사할 수 있어야 합니다.
live-bootstrap 같은 이상주의적인 프로젝트는 한 걸음 더 나아가, 원칙적으로는 읽을 수 있는 소스라 해도 생성된 소스는 완전한 소스 부트스트랩 체인에 아예 넣지 못하게 합니다. xz 사태에서 보았듯이 원칙적으로 읽을 수 있는 configure 스크립트에도 백도어가 들어갈 수 있기 때문입니다. 그런 프로젝트에서도 Eurydice는 소스에서 부트스트랩할 수 있을 테니, 러스트 컴파일러를 C로 옮기는 일을 평범한 빌드 단계로 처리한 다음 부트스트랩된 C 컴파일러로 컴파일하면 될 겁니다.
> 톰슨식 트로이 목마가 걱정된다면 부트스트랩 체인에 있는 모든 소스를 감사할 수 있어야 합니다.
감사해야 할 건 최종 결과물 아닌가요? 물론 최종 도구가 만들어 낸 어셈블리를 일일이 손으로 분석할 사람은 없겠지만요.
다시 말해 원래 소스 언어가 무엇이든 톰슨식 트로이 목마를 찾아내려는 노력에 어떤 가치가 있는지 저는 모르겠습니다. 안고 살아가야 하는 위험 가운데 하나라고 봅니다.
사실 "다양한 이중 컴파일(diverse double-compiling)"로 이걸 막는 방법이 이미 나와 있습니다.
"진짜" 컴파일러를 신뢰할 수 있는 다른 컴파일러로 컴파일해서 "독립 진짜 컴파일러"를 만듭니다.
"진짜" 컴파일러를 자기 자신과 "독립 컴파일러"로 다시 컴파일합니다. 결과물이 서로 다르면 문제를 찾아낸 겁니다.
소스 수준 변환은 이 "독립 컴파일러"를 만드는 훌륭한 방법입니다.
https://en.wikipedia.org/wiki/Backdoor_(computing)#Countermeasures
의견을 가질 자격이야 있으시겠죠. 그런데 그럴 만한 이력은 있으세요?
무슨 이력을 말씀하시는 거죠? 그리고 제가 뭘 말했다고 그렇게 건방진 대답을 들어야 하나요?
Stabbles 님이 정확히 짚으셨습니다! (HN의 다른 계정 50%가 봇인 것과 달리, 올려 주신 다른 훌륭한 글들을 보면 봇이 아니라는 게 분명하다는 점도 감사드립니다!)
신뢰와 보안이 한 측면이라는 건 맞습니다...
하지만 이 모든 일의 또 다른 중요한 측면은 후손을 위해 이해할 수 있고, 유지보수할 수 있고, 통제할 수 있는 소프트웨어와 AI를 밑바닥부터 만드는 것입니다...
먼저 미래에는 컴파일러의 소스 코드가 더는 존재하지 않고, 미래의 AI(그때쯤이면 그 AI 역시 블랙박스일 테죠)가 쓰는 블랙박스 컴파일러만 남았다고 가정해 봅시다... 그 시점에서 인류는 통제력을 잃습니다! 블랙박스 컴파일러 없이는, AI 없이는 어떤 소스도 컴파일할 수 없고, 그렇게 컴파일된 모든 소스 코드는 원래 컴파일러에 심어진 정체불명의 무언가에 좌우될 테니까요!
달리 말하면, 인류가 새 AI를 만드는 데 AI를 필요로 하게 된다면(즉 과학자, 컴퓨터 과학자, 프로그래머를 비롯해 누구도 트랜지스터부터 시작해서 어셈블러와 컴파일러, 신경망에 이르기까지 그 위의 모든 추상화 계층을 통제하며 밑바닥부터 AI를 만드는 법을 모르게 된다면)... 인류는 끝장입니다. 그 시점의 인류는 AI를 처음부터 다시 만들 줄도, 단계마다 통제할 줄도 모르니, 말 그대로 AI에 대한 통제력을 잃은 셈이니까요!
미래의 인류가 그런 상황에 놓여서는 안 됩니다.
미래의 인류에게는 모든 추상화 수준에서의 투명성이 반드시, 절대적으로 필요합니다... 블랙박스는 어디에도 있어서는 안 됩니다!
또한 미래의 인류에게는 하드웨어와 소프트웨어 추상화의 각 계층을 이해하고 관리하고 통제할 수 있게 해 줄 교육도 필요하지만, 그건 이 HN 글의 범위를 벗어나는 이야기네요... :-)
아무튼 Stabbles 님, 정확히 이해하셨습니다!
컴파일 타임뿐 아니라 런타임에도 러스트를 더 안전하게 만들어 주는 기능, 예를 들어 경계 검사 같은 것을 글에서 더 다뤘으면 좋았겠다 싶습니다. IR 단계에 이르면 어차피 똑같을 것 같긴 한데, 그걸 다시 C로 끌어올린 모습을 보면 좋을 것 같아서요.
그런 부분도 다루고 있다고 생각합니다. 러스트가 추가하는 검사는 (원래 소스에 직접 쓰여 있든 러스트 의미론에서 암묵적으로 따라오든) 모두 IR에 들어 있을 테고, 그 검사들이 컴파일된 C 출력에도 그대로 들어가게 됩니다. Eurydice의 이상적인 출력은 러스트가 제공하는 것과 의미론도 안전성도 똑같은 코드일 겁니다. 러스트 컴파일러를 지원하지 않는 플랫폼에서 러스트로 코딩하는 것이 사용 목적이니 꽤 중요한 목표이기도 하고요.
아, 소프트웨어가 아니라 글을 말한 거였어요. 소프트웨어는 당연히 전부 다루고 있겠죠.
"이 때문에 타입만 다른 함수 구현이 여러 개 생길 수 있는데, C답게 쓰려면(idiomatic) 보통 매크로나 void * 인자를 쓰는 편이 낫다."
"Idiomatic"은 터무니없는 동어반복이라, 언어를 논하는 사람들이 이제 그만 썼으면 하는 말입니다.
진지하게 쓰인 코드 가운데 idiomatic하지 않은 코드는 컴파일러나 인터프리터가 이해하지 못하는 코드뿐입니다.
특정 스타일이 보편적으로 우월하다고 주장하는 건 얄팍하고 생각이 없는 짓입니다.
일부러 난독화하는 건 또 다른 문제고요.
s/idiomatic/normative
컴퓨터 쪽 사람들은 이름을 붙일 때 자기가 쓰는 단어의 뜻을 어렴풋이만 알고 붙이거든요. 그냥 받아들이셔야 해요.
그렇긴 한데, 하필 C를 논하는 자리에서 쓰기엔 이 표현이 여느 때보다 더 얄팍하게 느껴졌습니다.
정말 열정이 넘치면 난독화된 C를 쓸 수도 있습니다. 그리고 그게 뭔지는 보면 사람들이 대체로 동의하죠.
그런데 idiomatic 은요? 그게 뭔지 정의해 보시죠, 행운을 빕니다.
파이썬에서는 이 용어가 어느 정도 자리를 잡았고 쓰임새도 좀 더 분명합니다. 객체 방식으로 하는 일이 있고, 표현식 방식으로 하는 일이 있고, 그런 방식들이 서로 맞물리는 패턴이 아주 훌륭하게 작동하니까요.
하지만 그만한 수준의 제도적 설계가 없는 다른 언어에서 그 표현을 쓰면 현실과 동떨어진 사람처럼 들릴 수밖에 없습니다.
제품 이름 한번 기막히게 지었네요 :D
"You're da C"(유어 다 씨)
"you read da c"(유 리드 다 씨)
저는 hadestown 덕분에 이 발음을 알게 됐어요.
사실은 "you're rid o(f) c"(C에서 벗어났다) 쪽에 더 가깝죠. 반대 방향으로 컴파일하게 만들 생각은 안 해보셨나요?
죄송하지만 이 스레드보다는 비주류 수학 스레드가 차라리 해독하기 더 쉬울 정도예요.
누가 쉽게 좀 설명해 주실 분 없나요?
"Eurydice"는 위쪽 댓글들에 적힌 것과 대략 비슷하게 발음해요. ("you-ree-dice"가 아니에요.)
"Eurydice"는 그리스어니까 [eu̯.ry.dí.kɛː]에 더 가깝게 발음하지 않나요? "유"와는 전혀 비슷하지 않을 것 같은데요.
맞아요, "you-ri-di-cee"죠.
원이 완성되었군요.
이렇게 계속 왔다 갔다 컴파일하면 어느 순간 순환에 빠질지, 아니면 계속 불어나기만 할지 궁금하네요.
이론적으로는 원이 되어야 하고, 계속 불어난다면 버그가 있다는 뜻이겠죠.
D와 C++에도 이런 게 있으면 좋겠네요.
왜요?
C++는 C로 변환하는 전처리기로 시작했고, 요즘은 쓸 만한 C 컴파일러가 전부 C++로 작성되어 있습니다.
D는 이미 쓸 수 있는 백엔드가 충분하고요.
부트스트랩 경로를 유지하려고 옛 버전 GCC를 아직도 관리하는 사람들이 있어요.
저는 들여다볼 만한 중간 결과물이 생긴다는 점도 도움이 되리라고 생각해요. 그러니 C++에서 C로 가는 컴파일러가 있으면 유용하겠다는 데 동의합니다.
중간 결과물이라면 어셈블리 소스 코드로 컴파일한 결과가 있고, C++ 인사이트 같은 도구도 있습니다.
그런 분들은 이제 그만 넘어가야 합니다. 최소한 C++98 컴파일러가 없는 쓸 만한 플랫폼은 하나도 없고, 컴파일러를 부트스트랩하는 다른 방법도 있으니까요.