전체 글

리누스 토르발즈가 AI로 Intel Xe GPU 검은화면 버그를 잡은 과정

xXfacebook페이스북

리누스 토르발즈가 Intel Xe GPU 드라이버의 검은화면 버그를 직접 잡았습니다. 실제 수정은 round_up 을 round_down 으로 바꾼 한 줄입니다. 그런데 그 한 줄에 닿기까지 디버그 정보를 붙인 패치 24개와 커널 부팅 18회가 있었고 그 과정을 AI가 상당 부분 거들었다고 커밋 메시지에 적혀 있습니다. Intel의 최신 Battlemage GPU를 쓰거나, 커널·드라이버에서 메모리 경계를 다루는 코드를 짜는 사람이라면 읽어 둘 값이 있는 사례입니다.

먼저 하나 짚습니다. "AI가 리눅스 커널 버그를 고쳤다"는 요약이 돌 수 있는데, 커밋 원문을 보면 그렇게까지 말하기는 어렵습니다. 원인을 규명하고 방향을 정하고 포기하지 않은 쪽은 리누스이고 AI가 맡은 것은 궂은일(디버그 코드 추가와 로그 분석)과 커밋 메시지 작성이었습니다. 이 글은 커밋과 그 메시지에 실제로 적힌 내용만 정리합니다. 추측한 모델명이나 아직 확정되지 않은 반영 버전 같은 것은 다루지 않습니다.

Intel Battlemage GPU를 콜드부트할 때마다 화면이 검게 죽고 gdm이 무한히 재시작되는 증상을 나타낸 그림

Battlemage G21에서 콜드부트마다 컴포지터 첫 제출이 폴트를 내고 gdm이 무한 재시작에 빠진다.

무슨 버그였나 — Battlemage에서 검은화면과 gdm 재시작 루프

증상은 명확했습니다. Intel Battlemage G21(16GiB) 계열 GPU에서 콜드부트를 할 때마다 화면이 검게 죽고, gdm(그놈 디스플레이 매니저)이 무한히 재시작되는 루프에 빠졌습니다. 데스크톱이 아예 올라오지 못하는 상태입니다.

무슨 일이 벌어졌는지는 이렇습니다. 콜드부트마다 Mesa의 VM 레벨-3 페이지 테이블이 문제의 메모리 주소에 올라갔는데, 하필 그 자리가 압축 하드웨어가 소유한 영역과 겹쳤습니다. 압축 하드웨어가 그 페이지의 tail 부분을 덮어쓰자 컴포지터의 첫 제출이 폴트를 냈고 그것이 gdm 재시작 루프로 이어졌습니다. 나중에 그 예약 페이지를 들여다보니 0xcccc000000000000, 0xcc77000000000000 같은 압축 메타데이터 패턴이 실제로 찍혀 있었습니다. 메모리의 엉뚱한 곳을 압축 하드웨어가 자기 것으로 알고 쓰고 있었다는 증거입니다.

원인은 오프셋을 위로 올린 것 — round_up이 압축 영역을 VRAM으로 노출했다

근본 원인은 flat CCS 영역의 저장 base 오프셋을 다루는 방식에 있었습니다. flat CCS는 압축 하드웨어가 소유하는 저장 영역인데, 드라이버가 이 base 오프셋을 128K 경계로 위로 올림(round_up) 했습니다. 그 결과 진짜 base 주소와 올림된 값 사이에 낀 메모리가 VRAM 할당기에 "사용 가능한 자유 메모리"로 넘어갔습니다. 실제로는 압축 하드웨어의 땅인데, 할당기 풀에 들어가 버린 것입니다.

숫자로 보면 이렇습니다. Battlemage G21(16GiB)에서 flat CCS의 base raw 값은 0x3fafff800이었고, 이것을 128K로 올리면 0x3fb000000이 됩니다. 그 사이에 있는 page 0x3fafff000의 마지막 2KiB는 원래 CCS 영역인데, 올림 때문에 할당기 풀에 편입됐습니다. 앞 절에서 본 것처럼 하필 이 자리에 Mesa의 페이지 테이블이 배치되면서 충돌이 났습니다. 커밋 메시지의 AI 작성 설명부는 이 함정을 한 문장으로 요약했습니다. "'사용 가능한 메모리는 여기서 끝난다'를 뜻하는 한계값을 위로 올리면, 진짜 base와 올림값 사이의 무엇이든 자유 메모리로 공개되며 그 메모리는 압축 하드웨어의 것이다."

flat CCS의 진짜 base와 128K로 올림한 값 사이의 메모리가 VRAM 할당기 풀로 잘못 편입되는 구조를 나타낸 메모리 배치 그림

base raw 0x3fafff800을 128K로 올려 0x3fb000000으로 만들면, 그 사이 2KiB가 CCS 영역인데도 할당기 풀에 들어간다.

버그를 심은 건 '수정' 커밋이었다

여기서 흔히 오해하는 지점을 바로잡습니다. 이 round_up 한 줄은 처음부터 있던 코드가 아니라, 다른 문제를 고치려던 선행 커밋이 새로 넣은 줄이었습니다. 리누스 커밋의 Fixes: 3717339 태그가 그 커밋을 정확히 지목합니다.

문제의 선행 커밋 37173392(작성자 Matthew Auld/Intel, 커미터 Lucas De Marchi)는 GSM과 CCS 사이의 hole을 잡는 assert를 통과시키려고 round_up(offset, SZ_128K) 한 줄을 넣었습니다. 의도는 "SW가 가장 가까운 128K로 올려야 한다"는 것이었는데, 그 올림이 앞서 본 대로 압축 하드웨어 영역을 자유 메모리로 노출하는 검은화면 버그를 만들었습니다. 즉 버그를 심은 것은 악의적인 코드나 오래된 레거시가 아니라, 버그를 막으려던 수정 그 자체였습니다. 이런 사례는 "이전 시도가 부족했다"기보다, 수정 한 줄이 눈에 안 보이는 부작용을 부르는 게 얼마나 쉬운지를 보여줍니다.

선행 커밋이 hole assert를 막으려 넣은 round_up 한 줄이 오히려 검은화면 버그를 만들었고, 그것을 리누스 커밋이 Fixes 태그로 지목하는 흐름을 나타낸 그림

선행 커밋 37173392가 심은 round_up을, 리누스 커밋이 Fixes: 3717339 태그로 되짚어 뒤집는다.

리누스의 수정 — round_down으로 뒤집고 assert를 부등호로

리누스의 수정은 두 부분입니다. 커밋은 torvalds/linux@818bebeb이고 파일은 drivers/gpu/drm/xe/xe_vram.c의 get_flat_ccs_offset() 함수이며 규모는 18줄 추가·5줄 삭제입니다.

첫째, 오프셋을 올리는 대신 내립니다. round_up(offset, SZ_128K)을 round_down(offset, SZ_4K)으로 바꿨습니다. 압축 하드웨어가 소유하기 시작하는 그 아래 전부가 VRAM 할당기로 넘어가므로, 오프셋은 압축 하드웨어가 소유하는 첫 주소여야 하고 따라서 내림해야 한다는 것입니다. 둘째, 기존 assert를 교체했습니다. 원래 assert는 offset == (GSMBASE - ccs_size) 라는 동등 비교였는데, 리누스는 이것을 offset + ccs_size <= GSMBASE("CCS overlaps GSM.")라는 부등호 검사로 바꿨습니다.

/*
 * Everything below this offset is handed to the VRAM
 * allocator, so it has to be the *first* address the
 * compression hardware owns, rounded down. Rounding it up
 * publishes CCS storage as free memory.
 */
offset = round_down(offset, SZ_4K);

/*
 * CCS storage must not run into GSM. The old check compared
 * the offset against GSMBASE - ccs_size for equality, which
 * could not fail: that value is 128K aligned, so it agreed
 * with the rounded-up offset even when the base was not 128K
 * aligned - exactly the case this fixes.
 */
xe_assert_msg(xe, offset + ccs_size <=
    xe_mmio_read64_2x32(&gt_to_tile(gt)->mmio, GSMBASE),
    "CCS overlaps GSM.\n");

기존 assert가 이 버그를 못 잡은 이유가 핵심입니다. 동등 비교의 대상이던 GSMBASE - ccs_size 값은 128K로 정렬돼 있었습니다. 그런데 base가 128K로 정렬되지 않았을 때 이를 올림한 오프셋이 바로 그 정렬된 값과 정확히 일치해 버렸습니다. 다시 말해 이 assert는 문제 상황에서도 항상 통과했습니다. 잘못을 잡으라고 둔 검사가 잘못을 눈감아 주고 있던 셈입니다. 참고로 이 커밋에는 Cc: [email protected]가 붙어 있어 stable 백포트 대상입니다. 다만 정확히 어느 정식 커널 버전에 실리는지는 2차 보도의 주장이라, 여기서는 "다음 머지 윈도우와 stable 백포트" 수준으로만 둡니다.

'AI가 도운 디버깅' — 커밋 메시지가 남긴 것

이 사례가 화제가 된 진짜 이유는 커밋 메시지에 있습니다. 리누스는 커밋 메시지의 기술 설명부를 AI가 쓰게 했다고 밝혔습니다. 원문은 이렇습니다. "So credit where credit is due and I let the AI write the commit message above."(그러니 공은 공대로 돌리자면, 위 커밋 메시지는 내가 AI에게 쓰게 했다.)

디버그 과정에 대한 말미 노트는 더 솔직합니다. 원문과 번역을 그대로 옮깁니다.

And this was a debug session from hell, enormously helped by an AI doing much of the grunt-work. I'd like to call it my tireless helper, but the AI several times stated flat out that this was impossible and unsolvable and that we should just write a report about it. ... But while the AI was ready to give up several times, it did keep adding debug code and analyzing it faithfully when I pushed.

"지옥 같은 디버그 세션이었다. 궂은일 대부분을 AI가 해준 덕에 크게 도움받았다. 지치지 않는 조수라 부르고 싶지만, 이 AI는 여러 번 이건 불가능하고 풀 수 없으니 그냥 보고서나 쓰자고 단언했다. ... 하지만 몇 번이나 포기하려 했어도, 내가 밀어붙이면 계속 디버그 코드를 추가하고 성실히 분석해줬다."

정리하면, AI가 맡은 것은 디버그 코드를 붙이고 로그를 분석하는 궂은일과 커밋 메시지 작성이었습니다. 원인을 규명하고 여러 번 포기하려는 AI를 밀어붙이고 최종 판단을 내린 쪽은 리누스입니다. 그러니 "AI가 커널 버그를 고쳤다"는 요약은 원문을 왜곡합니다. 도구가 궂은일을 크게 덜어줬지만 문제를 붙들고 방향을 유지한 것은 사람이었다는 편이 정확합니다. 참고로 그 AI가 어떤 모델·제품인지는 커밋에 "an AI"라고만 적혀 있어 확인할 수 없습니다. 특정 제품명을 붙인 보도는 2차 출처의 주장이라 여기서는 옮기지 않습니다.

AI가 맡은 디버그 코드 추가·로그 분석·커밋 메시지 작성과, 리누스가 맡은 원인 규명·방향 결정·판단을 나눠 표시한 역할 분담 그림

AI는 궂은일과 커밋 메시지를 맡았고, 원인 규명과 고집은 사람이 맡았다.

내 코드에 적용할 점

이 사례에서 자기 코드로 가져올 만한 교훈이 둘 있습니다. 첫째, "사용 가능한 범위의 한계값"은 위로 올리면 안 됩니다. 어떤 값이 "여기까지만 써도 된다"를 뜻한다면, 그 값을 정렬하려고 올림하는 순간 원래 없던 공간이 사용 가능한 것으로 열립니다. 반대로 "여기부터는 남의 것"을 뜻하는 경계는 내림해야 안전한 쪽으로 남습니다. 메모리 할당기, 버퍼 크기, 오프셋 계산에서 올림과 내림의 방향을 바꿔야 할 자리는 생각보다 자주 나옵니다.

둘째, 항상 통과하는 검사는 없는 것보다 나쁠 수 있습니다. 이 버그를 오래 숨긴 것은 잘못된 코드만이 아니라, 문제를 걸러 주리라 믿었던 assert였습니다. 동등 비교로 만든 검사가 특정 정렬 조건에서 늘 참이 되어, 정작 걸러야 할 케이스를 눈감아 줬습니다. 검사를 짤 때는 "이 조건이 실패할 수 있는 입력이 실제로 존재하는가"를 함께 물어보는 편이 좋습니다. 리누스가 동등 비교를 부등호로 바꾼 것도 같은 맥락입니다. 경계 조건을 다룰 때는 검사식 자체가 회피할 수 없게 세워져 있는지 한 번 더 확인할 값이 있습니다.

근거와 참고

이 글 공유하기xXfacebook페이스북