C 표준 라이브러리는 스레드 안전하지 않았고, 안전한 Rust도 크래시를 막지 못함
(edgedb.com)- EdgeDB가 네트워크 I/O를 Python에서 Rust로 옮기던 중,
reqwest기반 새 HTTP fetch 테스트가 ARM64 CI에서만 멈춘 것처럼 보이다가 실제로는 크래시함 - 코어 덤프 분석 결과 문제 지점은 새 HTTP 코드가 아니라
libc의getenv()내부였고, 환경 변수 배열을 순회하다 잘못된 포인터0x220을 읽으려다 실패함 - 다른 스레드가
openssl-probe경로에서SSL_CERT_FILE과SSL_CERT_DIR을 설정하며setenv()가environ을 재할당했고, 동시에 Python 오류 처리 경로가getenv()를 호출해 경쟁 조건이 생김 - Rust 코드에는 명시적
unsafe가 없었지만,std::env::set_var()가 전역 환경을 바꾸는 동안 다른 런타임이나libc직접 호출까지 Rust 내부 락으로 동기화되지는 않았음 - 해결은 Linux에서
reqwest의rust-native-tls/openssl백엔드 대신rustls로 옮기는 것이었고, Rust 2024 edition과 glibc도 이 계열 문제를 줄이는 방향으로 바뀌고 있음
ARM64 CI에서만 드러난 크래시
- EdgeDB는 네트워크 I/O 코드의 상당 부분을 Python에서 Rust로 이식하면서 새 HTTP fetch 기능을 만들고 있었음
- HTTP 클라이언트 라이브러리로
reqwest를 사용했고, 기능은 로컬과 x86_64 CI 러너에서는 통과했지만 ARM64 CI 러너에서 간헐적으로 실패함 - 처음에는 테스트 러너가 무기한 멈춘 것처럼 보였고, CI 로그에는 오류 없이 테스트 하나가 계속 실행 중인 상태로 남아 몇 시간 뒤 타임아웃됨
- 초기 가설은 Intel과 ARM64의 메모리 모델 차이였음
- Intel은 비교적 엄격한 메모리 모델을 가지며, 메모리 쓰기에 대해 모든 프로세서가 동의하는 전체 순서가 있음
- ARM은 더 약한 순서의 메모리 모델을 가지며, 쓰기가 서로 다른 스레드에 다른 순서로 보일 수 있음
Docker CI 환경에서 코어 덤프 추적
- 야간 CI 머신은 Amazon AWS에서 실행되어, 컨테이너 밖의 실제 root 사용자로 접속해
dmesg와 시스템 로그를 볼 수 있었음 - 컨테이너 안팎에서 멈춘 것으로 보인 PID를 찾았지만 해당 프로세스가 없었고, 이로써 데드락이 아니라 크래시였음이 드러남
- Docker 컨테이너는 프로세스 네임스페이스이기 때문에 코어 덤프가 Docker 호스트로 전달됐고,
journalctl에서python3프로세스의 코어 덤프를 확인함 - 처음
gdb로 코어를 열었을 때는 컨테이너 내부의.so파일이 없어 백트레이스가 쓸모없었음 - 컨테이너에서
/lib,/usr등을 복사하고gdb의solib-absolute-prefix를 설정한 뒤, 크래시 지점이libc.so.6의getenv()임을 확인함
getenv()가 읽은 깨진 환경 변수 포인터
- 전체 백트레이스는
getenv()→__dcigettext()→strerror_r()→strerror()→PyErr_SetFromErrnoWithFilenameObjects()흐름이었음 - 새 HTTP 코드가 직접 크래시한 것이 아니라, Python이 errno 기반 예외를 만들다가 gettext 관련 경로를 거쳐
getenv()를 호출함 - GLIBC 2.17의
getenv()구현은 POSIX의environ을char **목록으로 순회하고, 마지막NULL포인터까지 환경 변수 문자열 포인터를 검사함 - 디스어셈블리와 레지스터 상태상
getenv()는x19 = 0x220주소에서 바이트를 읽으려다 크래시함0x220은 명백히 유효하지 않은 메모리 주소였음environ자체를 검사했을 때 현재 환경 변수 목록은 일관되어 보였음
원인: setenv()와 getenv()의 경쟁 조건
setenv()는 멀티스레드 환경에서 안전하게 호출할 수 있는 함수가 아니며,libc의getenv()에서 이상한 크래시를 일으키는 문제가 여러 번 재발견되어 왔음- 디스어셈블리와 C 코드를 대조한 결과,
x20은environ배열을 걷는 포인터ep에 해당했음 - 크래시 당시
x20은0x248b5000이었고, 현재environ은0x28655750으로 약 60MB 뒤에 있었음 - 예전 환경 배열 주변 메모리와 현재
environ을 비교하자 마지막 차이가SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt와SSL_CERT_DIR=/etc/ssl/certs항목에서 나타남 - 다른 스레드가
setenv()호출 중environ을 옮겼고,getenv()는 이전 환경 배열을 계속 읽는 use-after-free 상황에 빠진 것으로 볼 수 있었음
openssl-probe와 TLS 백엔드 연결
rust-native-tls관련 오래된 이슈에서openssl-probe가 시스템 인증서를 찾기 위해SSL_CERT_FILE과SSL_CERT_DIR환경 변수를 설정한다는 단서를 찾음- Linux에서
rust-native-tls의openssl백엔드를 사용하면 해당 경로가 호출됨 - 문제가 된
openssl-probe코드는 명시적인unsafe없이env::set_var()로 두 환경 변수를 설정함SSL_CERT_FILESSL_CERT_DIR
- 이 조합 때문에 unsafe 없는 Rust 코드가 같은 프로세스 안의
libc사용과 나쁘게 상호작용해 크래시를 만들었음
ARM64 Linux에서 재현된 이유
- 이 크래시는
setenv()가realloc으로 환경 배열을 옮기는 순간, 다른 스레드가getenv()를 호출해야 발생함 - 재현에는 여러 조건이 동시에 맞아야 했음
- 환경 변수 개수가
realloc을 유발할 만큼 맞아야 함 - 관련 없는 I/O 실패가
asyncio에 잡혀야 함 - Python 오류 처리 경로가
LANGUAGE환경 변수를 얻기 위해getenv()를 정확히 같은 시점에 호출해야 함
- 환경 변수 개수가
- 잘못된 값
0x220은 64비트 워드 기준 예전 환경 크기와 가까웠음0x220 / 8 = 68- 예전 환경 블록의 종료
NULL자리에 이 값이 덮어써졌고, 이는 시스템 malloc이 free block 크기를 표시하기 위한 값으로 보였음
- 많은 선행 조건이 필요했기 때문에 단일 플랫폼에서 상당히 재현 가능했던 것 자체가 운이 좋은 상황이었음
ARM64 디스어셈블리에서 확인한 단서
getenv()디스어셈블리에서x20이 바뀌는 지점이 잘 보이지 않아 혼란이 있었음- 핵심은 AArch64의 pre-index 주소 지정 모드였음
ldr x19, [x20, #8]!는 다음과 같이 동작함x19 = *(x20 + 8)x20 = x20 + 8
- 이 주소 지정 모드 때문에
x20은 명시적으로 왼쪽에 쓰이지 않아도 환경 변수 포인터 배열을 순회하고 있었음
적용한 수정과 관련 프로젝트 변화
- 최종적으로 Linux에서
reqwest의rust-native-tls/openssl백엔드 대신rustls로 마이그레이션하기로 결정함 - 원래 native TLS 백엔드를 선택한 이유는 Python 코드를 Rust로 옮기는 동안 TLS 엔진 두 개를 함께 싣는 일을 피하려는 것이었음
- 이번 문제 이후 단기적으로 TLS 엔진 두 개를 싣는 것이 괜찮다고 판단함
- 대안으로는
try_init_ssl_cert_env_vars()의 첫 호출을 Python의 GIL을 잡은 상태에서 수행하는 방법도 있었음- Rust에는 Rust 코드끼리 환경을 읽고 쓰는 경쟁을 막는 내부 락이 있음
- 하지만 다른 언어 코드가
libc를 직접 사용하는 경우까지 막지는 못함 - GIL을 잡으면 최소한 Python 스레드와의 경쟁은 막을 수 있음
- Rust 프로젝트는 이 문제를 이미 인식했고, 2024 edition에서 환경 setter 함수를
unsafe로 만들 계획임 - glibc 프로젝트도 최근
realloc을 피하고 오래된 환경 배열을 누수시키는 방식으로getenv()의 스레드 안전성을 높이는 변경을 추가함
댓글과 토론
Hacker News 의견들
-
여기서 가장 큰 포인트는 Rust 다음 에디션에서 환경 변수 설정 함수가 unsafe가 된다는 것임
운이 좋으면 이런 크래시를 유발하는 크레이트들까지 영향이 내려갈 것 같고, 그동안 upstream에는 https://github.com/alexcrichton/openssl-probe/issues/30 이슈가 올라감- 하지만 근본 문제, 즉
getenv와setenv또는unsetenv를 서로 다른 스레드에서 안전하게 호출할 수 없다는 점은 실제로 고쳐지지 않음
신뢰할 만한 해결책은 이 함수들이 반드시 뮤텍스를 잡도록 바꾸는 것뿐으로 보임 - “요즘 이 문제의 최선의 해법은 rustls 같은 라이브러리를 쓰고 이 크레이트를 그만 쓰는 것”이라는 라이브러리 작성자의 판단은 합리적이라 반가움
안타깝게도 생태계는 그렇지 않음: https://github.com/seanmonstar/reqwest/blob/master/Cargo.tom... - 사람들은
____UNSAFE_payattention__nevermindthatthisappears50timesinthisfile___같은 블록과 접두사를 무시하도록 훈련됨
웹 프레임워크에서도 비슷해서 Vue에는v-html지시자가 있고 React에는dangerouslySetInnerHTML이 있는데, 이 면에서는 Vue 쪽이 확실히 낫다고 봄
- 하지만 근본 문제, 즉
-
Rust 표준 라이브러리의
set_var와remove_var는 다음 에디션인 2024 에디션에서 제대로unsafe {}블록을 요구하게 됨
문서도 이제 안전성 문제를 언급하지만, 애초에 이 함수들을 safe로 만든 건 실수였고 더 고수준 언어들도 저지른 실수임
https://doc.rust-lang.org/stable/std/env/fn.set_var.html
glibc에는 환경이 수정되는 더 많은 경우에getenv를 안전하게 만드는 패치가 있지만, C는 여전히environ에 직접 접근할 수 있어서 수정이 일어나는 상황에서는 완전히 안전해질 수 없음: https://github.com/bminor/glibc/commit/7a61e7f557a97ab597d6f...- glibc가 이제 예전 버전을 유지하고 지수적 크기 조정 정책을 채택한다는 점이 놀라움
활성 환경 변수마다 상각 기준 상수 크기의 메모리 누수가 생기지만, 변수 자체에도 이미 그런 누수가 있고 심지어 길이에 따라 달라지며 더 이상 쓰지 않는 값도 포함된다고 함
API상 올바른 프로그램에서도 이 때문에 메모리가 무한히 증가하는 병적인 사용 사례가 분명 있을 것 같음
API를 여러 스레드에서 써서 규칙을 어긴 프로그램을 고치기 위해, API를 따르는 프로그램에 무한 메모리 증가라는 버그를 도입하는 모양새라 흥미롭지만 찜찜함. 교조보다 실용주의라는 느낌 - 표준 라이브러리 구현이 동기화를 처리할 수 있다면 왜
unsafe를 요구해야 하는지 의문임
- glibc가 이제 예전 버전을 유지하고 지수적 크기 조정 정책을 채택한다는 점이 놀라움
-
C 표준 라이브러리 유지보수자들이
setenv를 다중 스레드 안전하게 만드는 데 반대하더라도, 최소한 POSIX 안에서든 사실상 표준을 먼저 만들고 POSIX가 나중에 받아들이게 하든 새로운 스레드 안전 API는 정의돼야 함
아무것도 할 수 없는 이유를 설명하는 데 쓰는 시간으로 이 문제를 고쳤다면, 예전setenv를 대체하고 많은 소프트웨어 프로젝트에서 폐기·제거할 수 있었을 것임
glibc가 이 문제를 사실상 없애려는 변경을 하고 있다는 점을 보면, Musl 안에서는 고칠 수 없다는 Musl 유지보수자의 말도 설득력이 부족함- 가장 큰 문제는 스레드 안전 API가 없다는 게 아니라
extern char **environ;이 존재한다는 점임
environ이 공개적으로 접근 가능한 한setenv와getenv가 아예 쓰인다는 보장도 없음. 둘은 필수가 아니기 때문임
environ을 없앨 수 있다면setenv와getenv를 스레드 안전하게 만드는 건 꽤 간단함. 없앨 수 없다면 불가능하지만, 완전한 해법은 아니더라도setenv와getenv를 스레드 안전하게 만드는 게 개선이라는 주장은 가능함 - 환경을 인자로 받지 않거나 실행 파일을 찾기 위해
PATH를 검색하는 모든exec()함수에도 잠금이 필요해질 것 같음 - 하위 호환 방식으로는 고칠 수 없다고 결론 낸 전문가들보다 더 잘 안다는 점은 설득되지 않음
- 가장 큰 문제는 스레드 안전 API가 없다는 게 아니라
-
Linux에서 환경 변수 관련 버그를 맞는 건 일종의 통과의례 같고, 다른 Unix에서는 이상하게 덜 문제가 되는 편임
Linus와 커널은 POSIX 버그를 실제로 안 터지게 만드는 식으로 실용적으로 고치는데, glibc는 사람들이 문제를 조금이라도 완화하려고 한 지 수십 년이 지나도 뒤처져 있다는 점이 좀 웃김
TZ같은 온갖 골칫거리가 있는 건 맞지만,getenv_r()를 제공하고setenv()와 동기화하며getenv()사용 시 컴파일·링크 단계에서 경고만 했어도 문제 상당수가 사라졌을 것임
더 나아가 환경 포인터를 읽기 전용으로 두는 쓰기 시 복사(COW) 방식까지 할 수도 있었음
대신 문제를 개별 애플리케이션에 떠넘겼는데, 애플리케이션 작성자는 의존성이 뭘 하는지 거의 알 수 없어서 큰 실수임. 오래전에 내가 처했던 상황도 그랬고, 당시 폐쇄 소스 라이브러리 벤더는 그 장난감 Unix 클론인 Linux를 그만 쓰라고 했음- “Linux에서 환경 변수 관련 버그가 있고 다른 Unix에서는 덜 문제”라는 판단은 어떻게 나온 건지 모르겠음
문제는 구현이 아니라 API 자체임.setenv(),unsetenv(),putenv(), 특히environ은 다중 스레드 프로그램에서 본질적으로 안전하지 않음
getenv_r()도 완전히 구할 수는 없음. 한 스레드가 환경 변수의 예전 값을 제공된 버퍼로 복사하는 동안 다른 스레드가setenv()를 호출할 수 있기 때문임
물론getenv()로 받은 뒤 다른 스레드가setenv()를 호출해 그 메모리를 무효화하는 경우는getenv_r()가 고치지만, API를 깨는 다른 호출들을 막을 방법은 없음
libc가getenv()/setenv()/putenv()/unsetenv()안에서 뮤텍스를 잡는 식으로 일부 문제를 완화할 수는 있지만,getenv()가 반환한 값이 호출 코드가 쓸 만큼 오래 유효하다고 libc가 보장할 방법은 여전히 없음
environ에 대한 직접 접근을 안전하게 만들 좋은 방법도 없음.environ을 스레드 로컬로 만들 수는 있겠지만, 그러면 스레드마다 환경 관점이 영구적으로 어긋날 수 있고getenv_r()호출과environ직접 확인 결과가 달라질 수도 있음
여기서 하위 호환성을 지키기는 정말 어렵고, 함수들을 보호하는 뮤텍스를 추가하는 것만으로도 기존 프로그램의 의미가 달라져 깨질 수 있음
- “Linux에서 환경 변수 관련 버그가 있고 다른 Unix에서는 덜 문제”라는 판단은 어떻게 나온 건지 모르겠음
-
예전에
setenv가 끔찍하다는 글이 있었음: https://www.evanjones.ca/setenv-is-not-thread-safe.html
토론도 있었고 첫 댓글부터 Rust에서 문제를 일으킨다는 내용이었음: https://news.ycombinator.com/item?id=38342642- 그건 이미 알려진 사실임
여기서 나머지 문제 대부분은 개발 환경으로 보임. Amazon 데이터센터의 원격 머신에서 Docker를 써서 테스트하고, 그 장비가 프로세스 크래시를 보고하지 못했음
게다가 컨테이너 안에 역추적을 얻을 충분한 디버그 심볼 정보도 없었음. 첫 실패 때 깨끗한 역추적을 받았다면 바로 명확했을 것임
애초에 왜setenv를 쓰는지도 의문임
- 그건 이미 알려진 사실임
-
이 얘기를 보니 예전 동료 몇 명이 굉장히 믿었던 12-factor app 운동이 떠오름. 그 “factor” 중 하나가 애플리케이션 설정은 환경 변수로 해야 한다는 것이었음
항상 좀 어리석다고 생각했는데, 설정 방식이 평평한 이름공간에 문자열 타입 값들을 바구니처럼 담는 구조이기 때문임
getenv()/setenv()/environ의 위험도 환경 변수를 설정에 쓰지 말아야 할 강한 근거라고 봄
물론 항상 훌륭하고 지원 잘 되는 대안이 있는 건 아님. 나는 설정 파일을 선호하고, 개발·스테이징·프로덕션 값만 채워 넣는 템플릿 설정도 쓸 수 있음. 보통은 단점과 함정이 있어도 YAML을 쓰는데, 더 나은 설정 파일 형식이 있을 수는 있어도 YAML이 환경 변수보다는 훨씬 낫다고 봄- Windows와 Microsoft를 향한 강한 반감이 많지만, 시간이 지나면서 그들의 API 설계가 정당화되는 경우가 꽤 있음
NT에서는 환경 변수를 타입화하고 템플릿화할 수 있고, 이름공간이 있는 설정 데이터베이스인 레지스트리도 있음. 장황하고 이상하긴 하지만 말임
게다가 MSVC는 거의 모든 표준 라이브러리 함수의 스레드 안전 버전을 제공함
새 C/C++ 개발자들이 MSVC의 POSIX 호환성 부족을 한탄하는 걸 자주 듣지만, 그게 실제로 무엇을 뜻하는지 깊이 생각하지는 않는 듯함. 그저 1990년대에 작성된 C 프로그램과 교차 호환되길 바라는 것에 가까움 - 환경 변수에 대해 비슷한 우려가 있음. 어디서든 읽을 수 있다는 점이 싫음
함수의 시그니처만으로 동작을 추론하는 능력을 방해하고, 순수 함수일 수 있었던 많은 함수를 불순하게 만듦
어떤 프로세스에서도 환경 변수는 쓸 수 없고 한 번만, 변수별로가 아니라 일괄로 한 번만 읽을 수 있게 앱을 표시하는 언어 기능이 있다면 어디서나 쓸 것 같음 getenv()자체는 완전히 괜찮고, 문제는setenv()임
이론적으로는 그 신비로운 앱이 시작되기 전에 환경이 설정되므로 이걸 쓸 필요가 없어야 함
하지만 평평한 이름공간, 문자열 값, 어떤 라이브러리와 모듈이 들어오는지도 모르는 상태에서 모두가 공유하는 자유로운 전역 공간이라는 점은setenv()의 안전성 문제가 없더라도 좋은 생각이 아님- “12-factor app”에는 프로세스가 살아 있는 동안 환경을 읽기 전용으로 취급해야 한다는 부록이 붙어야 함
여기서 사람들이 말하는 문제 대부분은 환경을 변경 가능한 전역 상태용 키-값 저장소처럼 남용하려는 데서 오는 듯함. 왜 굳이 그러고 싶은지 모르겠음
JVM은 환경을 사실상 불변으로 취급하고, SoundCloud 같은 Scala·Java 사용 회사들이 12-factor app 운동에 영향을 줬을 가능성도 있음. 나는 환경이 바뀌거나 스레딩 문제를 일으키는 일을 겪어본 적이 없음
환경이 바뀌더라도 JVM 시작 시 만들어진 불변 복사본은 그대로이고, 일반 Java API로 환경과 상호작용하는 코드는 그 수정을 보지 못함
설정 파일의 문제는 파싱이 프로세스별이라는 점임. 그래서 Linux/Unix가 그렇게 뒤죽박죽임. 도구마다 설정 관례와 메커니즘이 다르고 표준이 없음
Docker 생태계에서는 컨테이너 안에서야 뭘 하든 외부와의 인터페이스는 볼륨을 마운트해 앱마다 복잡한 설정 방식을 따르거나, 그냥 환경 변수를 쓰는 것임
요즘 Docker로 돌리는 대부분의 현대 소프트웨어는 환경으로 동작을 완전히 제어할 수 있을 정도로 Docker 친화적이고, 많은 경우 충분함
Docker Compose나 Kubernetes를 쓰면 프로세스 시작 방식을 정의하는 환경 변수 목록을 YAML 파일로 갖게 되므로, 어느 정도 원하는 구조가 나오기도 함. YAML을 좋아하진 않지만 충분히 작동하고, 문법 문제가 하루를 망칠 가능성은 크지만 대안들도 문제가 없는 건 아님 - 이건 사실 별개의 문제임. 환경 변수를 설정으로 읽어 들인 뒤 다시 건드리지 않으면 완전히 안전함
나도 12-factor app 스타일을 쓰지만, 앱에 들어온 뒤 환경 변수와 데이터를 검증하고 저장함. 그 뒤로는 아무 문제 없음
- Windows와 Microsoft를 향한 강한 반감이 많지만, 시간이 지나면서 그들의 API 설계가 정당화되는 경우가 꽤 있음
-
눈에 잘 띄지 않는 버그를 파고드는 훌륭한 글임
간헐적 버그, 아키텍처 특이성, 의존성 안에 숨어 있음, Rust, Python GIL, gettext까지 다 들어 있었음
이런 자세한 문제 해결 보고서는 직접 겪는 것에 가장 가까운 자료임. 의존성이 그걸 쓰고 있는데 내가 어떻게 알았겠냐는 상황에서는 “X 쓰지 말면 되지”라고 쉽게 말하기 어렵다 -
“야간 CI 머신은 Amazon AWS에서 돌고, 컨테이너가 아닌 진짜 root 사용자를 쓸 수 있다는 장점이 있다”면서, 동시에 “컨테이너 밖에는 필요한 파일이 없고 컨테이너는 매우 최소화돼 있어서
gdb를 쉽게 설치할 수 없다”고 함
이제 사람들은 클라우드와 컨테이너 없이 로컬에서 빌드하고 디버그하는 능력을 잃은 건가?- 맞음. 클라우드 SaaS가 사람들의 이해를 얼마나 왜곡했는지 충격적임
아주 사소한 일을 하려면 온갖 클라우드 복잡성과 배포 계층이 필요해졌음. PC 혁명을 100% 되돌려서 투박하고 비싼 메인프레임 컴퓨팅 시대로 돌아간 셈임
이유는 클라우드에 돈이 있기 때문이고, 클라우드는 DRM이기 때문임. 소프트웨어를 거기에 올리면 구독료를 받을 수 있고, 회피할 수 없으며 완벽한 종속을 영원히 유지할 수 있음. 사용자가 자기 데이터도 못 꺼내는 경우가 많음
제품 최적화를 위한 실시간 분석도 편하게 할 수 있음
컴퓨팅 아키텍처는 비즈니스 모델의 하류에 있음. 메인프레임이 처음 죽은 건 인터넷이 없고 PC가 더 저렴했기 때문이지만, 벤더가 종속 권력도 많이 잃었기 때문임
이제는 훨씬 수익성 높은 모델을 되살릴 방법이 생겼음. 사용자의 성가신 자유는 사라졌고, 솔직히 사용자가 그런 자유를 얻으면 종종 돈을 내지 않아서 품질 좋은 소프트웨어 사업이 성립하기 어려워지기도 함 - 이건 ARM에서만 터지는 무작위 메모리 오염 같은 문제임
로컬에서는 크래시를 재현하지 못했을 가능성이 큼. 개발자 머신은 대부분 x86이었고 거기서는 크래시가 나지 않았을 것임
크래시 처리는 더 잘했어야 하지만, 그들도 그 문제를 인식하는 듯하고 여기서 다룬 핵심은 아님 - 당연히 능력을 잃은 건 아니지만, 우리 머신에서는 크래시가 안 났음
- 문제가 나는 아키텍처를 실행하는 장치가 없을 가능성이 큰데 어떻게 로컬에서 디버그하겠나? 어차피 실패가 발생하는 실제 환경에서 디버그하는 게 훨씬 빠름
- 맞음. 클라우드 SaaS가 사람들의 이해를 얼마나 왜곡했는지 충격적임
-
변경 가능한 전역 상태는 악임. 친구라면 친구가 변경 가능한 전역 상태를 쓰게 두지 않음
환경 변수가 싫음. 이게 “Linux 방식”인데, 전염병처럼 피함. 강력 추천함
libc는 끔찍하고, 세상은 이제 넘어가야 함- 환경 변수는 프로세스 안에서 읽기 전용으로 취급하면 괜찮음
- “변경 가능한 전역 상태가 악”이라면 CPU와 RAM도 버려야 함
- 대안으로 뭘 제안하는지 궁금함
문제는 Linux도, 변경 가능한 전역 상태나 자원도, libc도 아님
문제는 일을 제대로 할 시간을 직장에서 받지 못한다는 것임. 예컨대 문제가 터지기 전에 GDB로 잡아내려면 상사가 코드와 그 코드가 건드리는 모든 것을 끈질기게 디버그하고 역추적할 시간을 줘야 함
반쯤 익은 코드에 돈이 너무 많이 몰려 있음. 슬프지만 사실임 - libc는 세상을 정보화 시대로 밀어 넣었음
- 선호하는 대안이 뭔지 궁금함
-
이런 문제가 너무 많아서 결국
LD_PRELOAD로getenv/setenv/putenv를 패치했음- 방금 glibc에 들어간 것처럼 환경을 누수시키는 고정 구현으로 한 건가?