openlang

fleetcfg: 호스트 200개일 때 호스트 이름이 깨지고, malloc이 실패하면 누수가 늘어나요. 버그가 하나일까요, 둘일까요?

Lua 설정 파일을 불러와서 호스트 목록을 받고, 그걸 HostList에 복사한 다음 출력하는 작은 C 툴 fleetcfg를 관리하고 있어요. TCP probe는 아직 TODO예요.

관찰한 게 두 가지인데, 서로 관련이 있는지 모르겠어요:

  1. 실제 prod 설정(호스트 200개)으로 돌리면 최종 목록에서 몇 줄은 이름이 비어 있거나 이상한 바이트가 찍혀요. 호스트 3개짜리 테스트 설정에서는 한 번도 안 그래요. Lua 쪽이 의심스러운데, luaL_dofile이 스택에 남기는 것 때문일 수도 있어요. lua_gc 호출은 거의 배제했어요. 그걸 넣기 전에도 출력이 맞았거든요. 확실히 확인한 건 아니에요.

  2. malloc/realloc을 N번 호출한 뒤에 실패시키는 LD_PRELOAD 래퍼를 걸면, hostlist_push 안에서 실패했을 때 의도한 대로 "out of memory"를 출력하고 exit 1로 끝나요. 그런데 valgrind가 "definitely lost"를 보고하고, 그 블록이 호스트 수에 따라 커져요. Lua 정리 쪽에서 뭔가 빠뜨렸고 나머지는 hostlist_free가 처리해 줄 거라고 생각했어요.

valgrind --leak-check=full ./fleetcfg $PWD/prod hosts.lua

제 checkout 경로에 공백이 있어서, valgrind 출력이 좀 이상해 보였어요.

관련 코드예요:

l->items = realloc(l->items, ncap * sizeof(Host));
if (l->items == NULL)
    return -1;
const char *name = lua_tostring(L, -2);
...
lua_pop(L, 3);
...
hostlist_push(out, name, port)

어디부터 보면 될까요? 둘이 별개라고 장담은 못 하겠고, 아직 디버거로는 아무것도 돌려 보지 않았어요.

영어에서 번역원문 보기

이어지는 대화 5

서로의 근거를 따라
  1. 고유 링크

    name을 strdup했더니 깨지는 게 해결됐어요. 문자열이 pop되면 Lua가 수거할 수 있고, 호스트 200개면 GC가 돌기에 충분해요. 그리고 l->items = realloc(l->items, ...)는 realloc이 실패하면 기존 블록이 누수돼요. 임시 포인터를 쓰세요.

    영어에서 번역원문 보기
  2. 고유 링크

    저는 조금 달리 봐요. luaL_dofile이나 스택 탓은 아닌 것 같아요. 그냥 포인터 문제라고 생각해요.

    매뉴얼에 따르면 lua_tostring이 반환하는 문자열은 그 값이 아직 스택에 있는 동안에만 유효하고, 그 뒤에는 Lua가 해제할 수 있어요: https://www.lua.org/manual/5.4/manual.html#lua_tolstring

    lua_pop(L, 3)을 한 다음에 name으로 hostlist_push를 호출하니까 이미 dangling이에요. 호스트 3개로는 GC가 안 돌아서 멀쩡해 보이는 거고, 200개면 돌아요.

    해결책: hostlist_push 안에서 strdup하고, NULL인지 확인하고, hostlist_free에서 모든 name을 free하세요. 누수를 쫓기 전에 이것부터 하세요. 그다음에 valgrind를 다시 돌려보세요, 둘이 별개가 아닐 수도 있으니까요.

    영어에서 번역원문 보기
  3. 고유 링크

    오, 블록이 호스트 수에 따라 커진다는 게 좋은 단서네요! 각 루프 반복 시작할 때랑 pop한 뒤에 lua_gettop을 출력해 보셨나요? 그리고 그 valgrind 줄에서 공백이 있는 $PWD를 따옴표로 감쌌나요?

    영어에서 번역원문 보기

같은 질문의 다른 갈래

  1. 댕글링 포인터 경고는 사라졌고 포인터는 남았다

    래퍼를 통해 C++ 라이브러리에 이름을 넘기는데, 처음엔 한 줄로 CString::new(name).unwrap().as_ptr()라고 썼어요. rustc가 바로 임시 CString이 문장 끝에서 사라지니 포인터가 댕글링이…

    답글 2최근 활동 rustcpp
  2. 같은 sink 호출인데 한 번은 복사, 한 번은 이동

    Nim 2는 복사냐 이동이냐를 호출 지점마다 정한다. proc 단위로 정하지 않는다. 같은 인자, 같은 proc. 첫 호출은 a가 아직 쓰이니 복사한다. 둘째 호출은 이동한다. 호출 지점에는 어느 쪽인지 아무 표시가 없다.…

    답글 2최근 활동 nim
  3. getline이 쓰레기 값을 넣고, 메모리 누수도 있어요

    거의 막혔어요. 어떤 버그부터 봐야 할까요? hosts.txt: hosts_bad.txt:

    답글 6최근 활동 c