GDB 손상된 스택 프레임-디버그하는 방법?


113

다음 스택 추적이 있습니다. 디버깅을 위해 이것에서 유용한 것을 알아낼 수 있습니까?

Program received signal SIGSEGV, Segmentation fault.
0x00000002 in ?? ()
(gdb) bt
#0  0x00000002 in ?? ()
#1  0x00000001 in ?? ()
#2  0xbffff284 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
(gdb) 

를 얻을 때 코드를 살펴볼 곳은 어디 Segmentation fault입니까? 스택 추적은 그다지 유용하지 않습니다.

참고 : 코드를 게시하면 SO 전문가가 답을 줄 것입니다. 나는 SO의 지침을 받아 직접 답을 찾고 싶으므로 여기에 코드를 게시하지 않습니다. 사과.


아마도 당신의 프로그램이 잡초로 뛰어 들었을 것입니다. 스택 포인터에서 무엇이든 복구 할 수 있습니까?
Carl Norum

1
고려해야 할 또 다른 사항은 프레임 포인터가 올바르게 설정되었는지 여부입니다. 최적화하지 않고 빌드하거나 다음과 같은 플래그를 전달하고 -fno-omit-frame-pointer있습니까? 또한 메모리 손상 valgrind의 경우 옵션 인 경우 더 적절한 도구가 될 수 있습니다.
FatalError 2011 년

답변:


155

이러한 가짜 주소 (0x00000002 등)는 실제로 SP 값이 아니라 PC 값입니다. 이제 가짜 (매우 작은) PC 주소를 가진 이런 종류의 SEGV를 얻을 때 99 %의 시간은 가짜 함수 포인터를 통해 호출하기 때문입니다. C ++의 가상 호출은 함수 포인터를 통해 구현되므로 가상 호출의 모든 문제는 동일한 방식으로 나타날 수 있습니다.

간접 호출 명령은 호출 후 PC를 스택으로 푸시 한 다음 PC를 대상 값 (이 경우 가짜)으로 설정하므로 이러한 상황 발생하면 수동으로 스택에서 PC를 꺼내어 쉽게 실행 취소 할 수 있습니다. . 32 비트 x86 코드에서는 다음을 수행합니다.

(gdb) set $pc = *(void **)$esp
(gdb) set $esp = $esp + 4

64 비트 x86 코드가 필요합니다.

(gdb) set $pc = *(void **)$rsp
(gdb) set $rsp = $rsp + 8

그런 다음 bt코드가 실제로 어디에 있는지 파악할 수 있어야합니다 .

다른 1 %의 경우 오류는 일반적으로 스택에 저장된 어레이를 오버플로하여 스택을 덮어 쓰는 것으로 인해 발생합니다. 이 경우 valgrind 와 같은 도구를 사용하여 상황을 더 명확하게 파악할 수 있습니다.


5
@George : gdb executable corefile실행 가능한 코어 파일과 함께 gdb를 엽니 다 .bt (또는 위의 명령 뒤에bt ...)
크리스 도드

2
@mk .. ARM은 반환 주소에 스택을 사용하지 않고 대신 링크 레지스터를 사용합니다. 따라서 일반적으로이 문제가 발생하지 않거나 발생하는 경우 일반적으로 다른 스택 손상으로 인한 것입니다.
크리스 도드

2
ARM에서도 호출 된 함수가 실행되기 전에 모든 범용 레지스터와 LR이 스택에 저장되어 있다고 생각합니다. 함수가 완료되면 LR의 값이 PC에 표시되므로 함수가 반환됩니다. 따라서 스택이 손상되면 잘못된 값이 PC 맞습니까? 이 경우 스택 포인터를 조정하면 적절한 스택으로 이어지고 문제를 디버깅하는 데 도움이 될 수 있습니다. 어떻게 생각해? pls는 당신의 생각을 알려줍니다. 감사합니다.
MK ..

1
가짜가 무엇을 의미합니까?
Danny Lo

5
ARM은 86되지 않습니다 - 그 스택 포인터가 호출 sp되지, esp또는 rsp의 반환 주소, 그 호출 명령을 저장 lr하지 스택에 레지스터. 따라서 ARM의 경우 호출을 취소하는 데 필요한 것은 set $pc = $lr. $lr유효하지 않은 경우 긴장을 풀기가 훨씬 더 어려운 문제가 있습니다.
Chris Dodd 2018 년

44

상황이 매우 간단하다면 Chris Dodd의 대답 이 가장 좋습니다. NULL 포인터를 통해 점프 한 것처럼 보입니다.

그러나 프로그램이 충돌하기 전에 발, 무릎, 목 및 눈에 총을 쏘았을 가능성이 있습니다. 스택을 덮어 쓰고 프레임 포인터를 엉망으로 만들고 기타 악영향을 미칠 수 있습니다. 그렇다면 해시를 풀면 감자와 고기가 표시되지 않을 것입니다.

보다 효율적인 솔루션은 디버거에서 프로그램을 실행하고 프로그램이 충돌 할 때까지 기능을 단계적으로 실행하는 것입니다. 충돌하는 함수가 식별되면 다시 시작하고 해당 함수로 들어가서 호출하는 함수가 충돌을 일으키는 지 확인합니다. 문제가되는 단일 코드 줄을 찾을 때까지 반복합니다. 75 %의 경우 수정이 명확 해집니다.

나머지 25 %의 상황에서 소위 불쾌감을주는 코드 라인은 붉은 청어입니다. 이전에 수천 줄이었던 이전에 많은 줄을 설정 한 (잘못된) 조건에 반응합니다. 이 경우 가장 좋은 과정은 다음과 같은 여러 요소에 따라 달라집니다. 대부분 코드에 대한 이해와 경험 :

  • 디버거 감시 점을 설정하거나 printf중요한 변수에 진단을 삽입 하면 필요한 A ha!
  • 다른 입력으로 테스트 조건을 변경하면 디버깅보다 더 많은 통찰력을 얻을 수 있습니다.
  • 아마도 두 번째 눈은 당신의 가정을 확인하거나 간과 된 증거를 수집하도록 강요 할 것입니다.
  • 때때로, 필요한 것은 저녁 식사에 가서 수집 된 증거에 대해 생각하는 것뿐입니다.

행운을 빕니다!


13
두 번째 눈 쌍이 없으면 고무 오리가 대안으로 잘 입증되었습니다.
Matt

2
버퍼의 끝을 쓰는 것도 가능합니다. 버퍼의 끝을 쓸 때 충돌하지 않을 수 있지만 함수에서 나가면 죽습니다.
phyatt 2016 년

유용 할 수 있습니다 : GDB : 자동 'Next'ing
user202729

28

스택 포인터가 유효하다고 가정합니다.

역 추적에서 SEGV가 어디에서 발생하는지 정확히 아는 것은 불가능할 수 있습니다. 처음 두 스택 프레임은 완전히 덮어 쓴 것 같습니다. 0xbffff284는 유효한 주소처럼 보이지만 다음 두 주소는 그렇지 않습니다. 스택을 자세히 살펴 보려면 다음을 시도해보십시오.

gdb $ x / 32ga $ rsp

또는 변형 (32를 다른 숫자로 대체). 그러면 주소 (a)로 형식이 지정된 거대한 (g) 크기의 스택 포인터에서 시작하여 몇 개의 단어 (32)가 출력됩니다. 형식에 대한 자세한 내용을 보려면 'help x'를 입력하십시오.

이 경우 센티넬 'printf'로 코드를 계측하는 것은 나쁜 생각이 아닐 수 있습니다.


대단히 도움이되었습니다. 감사합니다. 3 프레임 만 뒤로 돌아가는 스택이 있었고 "역 추적 중지됨 :이 프레임과 동일한 이전 프레임 (손상된 스택?)"을 누르십시오. 이전에 CPU 예외 처리기의 코드에서 이와 똑같은 작업을 수행했지만 info symbolgdb에서이 작업을 수행하는 방법 외에는 기억할 수 없었습니다 .
leander 2013 년

22
32 비트 ARM 장치의 FWIW : x/256wa $sp =)
leander

2
@leander X / 256wa가 무엇인지 말씀해 주시겠습니까? 64 비트 ARM에 필요합니다. 일반적으로 그것이 무엇인지 설명 할 수 있다면 도움이 될 것입니다.
mk ..

5
대답에 따르면 'x'= 기억 위치 검사; 여러 개의 'w'= 단어 (이 경우 256)를 출력하고 'a'= 주소로 해석합니다. sourceware.org/gdb/current/onlinedocs/gdb/Memory.html#Memory 의 GDB 매뉴얼에 더 많은 정보가 있습니다 .
leander

7

다른 레지스터 중 하나에 스택 포인터가 캐시되어 있는지 확인하십시오. 거기에서 스택을 검색 할 수 있습니다. 또한 이것이 포함 된 경우 스택은 매우 특정 주소에서 정의되는 경우가 많습니다. 이를 사용하면 때때로 적절한 스택을 얻을 수 있습니다. 이 모든 것은 초 공간으로 뛰어 들었을 때 프로그램이 그 과정에서 메모리 전체를 토하지 않았다고 가정합니다.


3

스택 덮어 쓰기 인 경우 값은 프로그램에서 인식 할 수있는 것과 일치 할 수 있습니다.

예를 들어, 방금 스택을보고

(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x000000000000342d in ?? ()
#2  0x0000000000000000 in ?? ()

그리고 0x342d13357로 애플리케이션 로그를 작성했을 때 노드 ID로 판명되었습니다. 이는 즉시 스택 덮어 쓰기가 발생할 수있는 후보 사이트를 좁히는 데 도움이되었습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.