2013-03-13 10 views
0

내가 GAS 구문 I386 어셈블리의 단편을 포함하는 다음과 같은 기능이, 왜 확실하지, 0에 의해 분할 이 기능으로 EXC_I386_DIV 충돌이 발생했습니다.가스 조립 조각은

int res = MulDivRound(4096, -566, 400); 

나는이 함수는 0으로 나누는 원인이 무엇 일어나고 명확하게 볼 수 있습니다 : 확실히 그냥 4096 eax에 이동 한 후 곱하는 -566에 의해, 다음 분할 다음 호는 충돌을 생산 400으로 나누기 연산 결과의 두 구성 요소를 반환합니다. 누구든지 이것에 대해 밝힐 수 있습니까?

+0

디버거에서 코드를 단계별 실행하여 각 명령어 앞에 레지스터 값을 확인하십시오. – Michael

답변

5

부문/곱셈 지침 :이 RAX (결과)와 RDX (나머지)에서 적절한 결과를 제공 피연산자는 이고 서명되지 않은 코드는mul/div입니다. 당신이 정말 따라서 수행하는 작업은 다음과 같습니다

  1. 서명 -566 (0xfffffdca로 2 보수 32 비트)는이 17592183726080 결과 4096 (EDX:EAX에서 0xfff:0xffdca000)를 곱하는 부호 4294958538
  2. 로 해석됩니다. 당신이)
  3. 전체 64 비트 값이 상위 32 비트가 0xfff, 4095을 사실에 400이 아니라 인해 나누어을 "기대"로 그의 낮은 32 비트가 -2318336로 변환주의, 결과는 UINT32_MAX를 초과하고 예외입니다 높인.당신이 divl 전에 xor %%edx,%%edx를 삽입하여 상위 32 비트를 취소하면

작업이 성공하지만 당신이 기대하지 않는 것이 돌아갑니다 - 0xa3c066 결과 400에 의해 즉, 그것은 0xffdca000 (4292648960를) 분할을 (10731622)가 EAX, 나머지가 0xa0 (160)이 EDX이다.

기계가 지시 한 내용까지는 "정확하다"는 것이지만 예상 한 것은 아닙니다. 서명 된 번호를 사용하려면 imul/idiv이 필요합니다.

조립체 궁극적 다음으로 간략화 될 수있다 :

GCC 입력/출력으로 사용하는 레지스터를 지정할 수 있기 때문이다
__asm__ __volatile__ (
    "imull %3    \n" 
    "idivl %4    \n" 
    : "=a" (nRet), 
     "=&d" (nMod) 
    : "a"  (nNumber), 
     "mr" (nNumerator), 
     "mr" (nDenominator) 
    : "cc" 
); 

때문에 데이터가없는 이동은 모두 여기에 필요하다. 또한, "m" 제약 조건만으로도 인수를 스택에 강제 적용 할 때 64 비트에 차선 최적화 코드가 생성됩니다. 대안을 주면 생성 된 코드가 개선 될 것입니다.

편집 :nMod 제약을 "=&d"(nMod)으로 변경했습니다. gcc가 "초기 clobber"를 호출해야합니다. 즉, 모든 입력 피연산자가 사용/사용되기 전에 지정된 출력 레지스터가 덮어 쓰여지고 컴파일러에서 EDX에 입력 (특히 (nDenominator))을 전달하지 않도록 지시합니다. 그렇지 않으면 일어날 것이라고, 그것은 "흥미로운"실패 모드를 일으킬 것입니다. 에만 인 경우 "m"을 사용하면 nNumerator/nDenominator에 문제가되지 않지만 레지스터가 허용되면주의해야합니다.

Edit2 : 위의 코드는 물론 오버플로 예외에 대한 증거는 아닙니다. 당신은 여전히 ​​MulDivRound(INT32_MAX, 4, 2)처럼 그것들을 방아쇠를 당길 수 있습니다. 정당하게/당연히이 지시는 디자인된다. 그런 일이 발생하지 않도록해야한다면, [i]div 앞에있는 분모와 EDX/RDX을 비교하고 더 작은 경우를 처리하는 코드를 추가해야합니다.

+0

+1 "데이터가 필요하지 않습니다."라는 좋은 해결책이 있습니다. 일부 코드로 내 대답을 업데이트하려고했지만, edx에서 eax까지 MOV가 포함되었을 것입니다. –

+0

@ Andreas : 고마워. ' "= a"'resp. x86에서 '[i] mul'/'[i] div '연산 코드를 사용하는''= d' '(그리고/또는 입력에 대해 동일하다)는 잘 알려져있다. 다른 예를 들면 http://stackoverflow.com/a/10781271/512360. 특히'[div]에 대해 염두에 두어야 할 점은 여러분이 언급 한 것입니다. 대부분의 경우'EDX' /'RDX' 또는'EAX' /'RAX'를 부호 확장해야합니다 ('CDQ' /'CQTO' 명령)를 호출해야합니다. 'imul'이 이미 올바른 일을 해 냈기 때문에 여기 _specific_의 경우에는 필요하지 않습니다. –

4

0으로 나누기 오류가 발생하지 않지만 오버플로 오류이 발생합니다.

divlrdx:rax/operand (상위 단어가 rdx)이며, 결과는 eax이고 나머지는 edx입니다. 코드에서

당신은 rdx=4095rax=0와 끝까지, 그래서 당신은 188848542454601534668 remainder 320 결과 75539416981840613867520/400을 분할하려고합니다.

188848542454601534668은 32 비트 결과 레지스터 eax에 맞지 않아 0x 000a 3ccc cccc cccc cccc이므로 오버플로 오류가 발생합니다.

rax에는 사용자의 값이 4095이고 그 값이 rdx=0인지 확인해야합니다. 당신은 서명 사용하고

:이 코드에서 잘못된 몇 가지있다 ... 86에서

rax   0xa  10 
rdx   0x5f  95 
+0

답변 해 주셔서 감사합니다! 현재 나누기 명령 바로 전에'rax = 0x00000000ffdca000'과'rdx = 0x0000000000000fff'가 있습니다. 이 값을 나누기 명령의 올바른 레지스터로 가져 오려면 어떤 지시를 사용해야합니까? – benwad

+0

@ FrankH.의 코드를 참조하십시오. –