[trace-replay] io_submit()에서 비정상적인 메모리 접근
Closed this issue · 4 comments
io_submit() 함수가 실행될 때, 미리 설정된 buffer와 관련해서 발생하는 버그 같습니다.
buffer를 생성하는 부분(trace_replay.c:371)과
iocb에 해당 buffer를 설정하는 부분(trace_replay.c:392),
io_submit() 함수가 실행되는 부분(trace_replay.c:513)을 참고하여 디버깅해야 할 것 같습니다.
버그의 원인은 buffer가 가리키는 공간이 아닌 다른 공간을 io_submit()에서 접근해서 발생한 것 같습니다.
[valgrind log]
Syscall param io_submit(PWRITE) points to uninitialised byte(s)
at 0x4E3C687: io_submit (in /lib/x86_64-linux-gnu/libaio.so.1.0.1)
by 0x10B8AC: sub_worker (trace_replay.c:513)
by 0x50456DA: start_thread (pthread_create.c:463)
by 0x537EA3E: clone (clone.S:95)
Address 0x5663000 is 0 bytes inside a block of size 4,096 alloc'd
at 0x4C31E76: memalign (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
by 0x4C31F91: posix_memalign (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
by 0x10A859: allocate_aligned_buffer (trace_replay.c:136)
by 0x10B168: make_jobs (trace_replay.c:371)
by 0x10B850: sub_worker (trace_replay.c:509)
by 0x50456DA: start_thread (pthread_create.c:463)
by 0x537EA3E: clone (clone.S:95)
[trace_replay.c:371]
...
359 job = (struct io_job *)malloc(sizeof(struct io_job));
360 align_sector(t_info, &blkno, &bcount);
361 job->offset = (long long)blkno * SECTOR_SIZE;
362 job->bytes = (size_t)bcount * SECTOR_SIZE;
363 if (job->bytes > (size_t)MAX_BYTES)
364 job->bytes = MAX_BYTES;
365
366 if (flags)
367 job->rw = 1;
368 else
369 job->rw = 0;
370
371 job->buf = allocate_aligned_buffer(job->bytes);
...
[trace_replay.c:392]
...
387 #ifndef USE_RAND_BUF
388 if (job->rw)
389 io_prep_pread(&job->iocb, t_info->fd, job->buf,
390 job->bytes, job->offset);
391 else
392 io_prep_pwrite(&job->iocb, t_info->fd, job->buf,
393 job->bytes, job->offset);
394 #else
395 if (job->rw)
396 io_prep_pread(&job->iocb, t_info->fd, g_buf, job->bytes,
397 job->offset);
398 else
399 io_prep_pwrite(&job->iocb, t_info->fd, g_buf,
400 job->bytes, job->offset);
401
402 #endif
...
[trace_replay.c:513]
...
509 cnt = make_jobs(t_info, ioq, jobq, max);
510 if (!cnt && trace_eof(trace)) {
511 goto Timeout;
512 } else if (cnt > 0) {
513 rc = io_submit(t_info->io_ctx, cnt, ioq);
514 if (rc != cnt && rc < 0) {
515 int i;
516 for (i = 0; i < cnt; i++) {
517 free(jobq[i]->buf);
518 free(jobq[i]);
519 }
520 }
521 if (rc > 0)
522 t_info->queue_count += rc;
523 }
...
링크랑 유사한 문제인 것 같네요.
해당 문제를 확인해본 결과 해결책은 찾았습니다만 해결이 불가능한 이슈입니다.
당장엔 실행에도 문제가 없으니 닫겠습니다.
추가로, 향후 이 이슈를 확인하실 분들을 위해서 제가 생각하는 해결 불가능의 이유를 설명 드리자면 아래와 같습니다.
이 문제를 해결을 하기 위해서는 복잡하게 이래저래 변경할 필요없이 "수정 전"의 내용을 "수정 후"와 같이 바꿔주면 됩니다.
수정 전
131 /* allocate a alignment-bytes aligned buffer */
132 void *allocate_aligned_buffer(size_t size)
133 {
134 void *p;
135
136 posix_memalign(&p, getpagesize(), size);
137
138 if (!p) {
139 perror("memalign");
140 exit(0);
141 return NULL;
142 }
143
144 return p;
145 }
수정 후
131 /* allocate a alignment-bytes aligned buffer */
132 void *allocate_aligned_buffer(size_t size)
133 {
134 void *p;
135
136 posix_memalign(&p, getpagesize(), size);
137 memset(p, 0, size);
138
139 if (!p) {
140 perror("memalign");
141 exit(0);
142 return NULL;
143 }
144
145 return p;
146 }
하지만 "수정 후"로 바꾸니 또 다른 문제가 발생했습니다.
trace-replay 특성상 많은 I/O 요청을 메모리에 적재해놓았다가 디스크에 요청을 하기 시작합니다. 여기서 적재를 위한 메모리의 확보 과정에서 posix_memalign
을 사용하여 페이지 단위로 정렬된 메모리를 할당을 받습니다. 이것이 문제의 원인으로 보입니다.
먼저, 유닉스 계열은 아시다시피 CoW(Copy-on-Write)를 따릅니다. 그렇기 때문에 값을 변경하지만 않는다면 메모리를 일단은 할당을 받지 않습니다. 이는 posix_memalign
도 동일할 것이라고 생각됩니다.
이런 가정 하에서 memset
을 수행하게 되면 쓰기를 수행한 것이기에 메모리가 할당이 되게 되고, 이러면 어마어마한 양이 CoW의 혜택을 받지 못하고 할당이 되게 됩니다.
이렇게 할당 받은 메모리가 사용자의 물리적 메모리 크기를 넘게 되면 swap 영역으로 넘어가게 됩니다. 하지만 지금 할당에 사용하는 함수는 malloc
이 아니라 posix_memalign
이므로 swap이 벌어졌을 때, 후에 다시 사용하려고 디스크에서 가져오면 swap 정책에 따라서 성공할 수도 실패할 수도 있습니다.
단적으로, trace-replay를 "수정 후"와 같이 변경한 후에 가상 scsi 디스크를 만들어서 qemu 환경에서 수행해 본 결과 swap이 일어나는 순간 qemu가 Killed 되는 것을 확인할 수 있었습니다.
따라서, trace-replay의 근본적인 설계를 변경하지 않는 이상은 이를 해결하기란 어려울 것으로 생각됩니다.
아마 제 생각이 틀렸을 수도 있지만 지금으로는 이 문제의 해결책은 이게 최선이 아닐까 싶습니다.