Corrupção de memória em System.Move devido à alteração do modo 8087CW (png + stretchblt)
-
24-09-2019 - |
Pergunta
Eu tenho um estranho problema de corrupção de memória.Depois de muitas horas depurando e tentando, acho que encontrei algo.
Por exemplo:Eu faço uma atribuição de string simples:
sTest := 'SET LOCK_TIMEOUT ';
No entanto, o resultado às vezes se torna:
sTest = 'SET LOCK'#0'TIMEOUT '
Então, o _ é substituído por um byte 0.
Já vi isso acontecer uma vez (a reprodução é complicada, depende do tempo) na função System.Move, quando ela usa a pilha FPU (fild, fistp) para cópia rápida da memória (no caso de 9 a 32 bytes para mover):
...
@@SmallMove: {9..32 Byte Move}
fild qword ptr [eax+ecx] {Load Last 8}
fild qword ptr [eax] {Load First 8}
cmp ecx, 8
jle @@Small16
fild qword ptr [eax+8] {Load Second 8}
cmp ecx, 16
jle @@Small24
fild qword ptr [eax+16] {Load Third 8}
fistp qword ptr [edx+16] {Save Third 8}
...
Usando a visualização FPU e 2 visualizações de depuração de memória (Delphi -> View -> Debug -> CPU -> Memory) vi que estava dando errado ...uma vez...não consegui reproduzir no entanto...
Esta manhã li algo sobre o modo 8087CW e, sim, se isso for alterado para US$ 27F, recebo corrupção de memória!Normalmente custa $ 133F:
A diferença entre $ 133F e $ 027F é que $ 027F configura o FPU para fazer cálculos menos precisos (limitando-se a Double em vez de Extended) e manuseio diferente do Infiniti (que era usado para FPUs mais antigos, mas não é mais usado).
Ok, agora eu encontrei por que mas não quando!
mudei o funcionamento do meu AsmProfiler com uma verificação simples (para que todas as funções sejam verificadas ao entrar e sair):
if Get8087CW = $27F then //normally $1372?
if MainThreadID = GetCurrentThreadId then //only check mainthread
DebugBreak;
Eu "criei o perfil" de algumas unidades e dll's e bingo (veja a pilha):
Windows.StretchBlt(3372289943,0,0,514,345,4211154027,0,0,514,345,13369376)
pngimage.TPNGObject.DrawPartialTrans(4211154027,(0, 0, 514, 345, (0, 0), (514, 345)))
pngimage.TPNGObject.Draw($7FF62450,(0, 0, 514, 345, (0, 0), (514, 345)))
Graphics.TCanvas.StretchDraw((0, 0, 514, 345, (0, 0), (514, 345)),$7FECF3D0)
ExtCtrls.TImage.Paint
Controls.TGraphicControl.WMPaint((15, 4211154027, 0, 0))
Então está acontecendo no StretchBlt ...
O que fazer agora? É uma falha do Windows ou um bug no PNG (incluído no D2007)?Ou a função System.Move não é à prova de falhas?
Observação: simplesmente tentar reproduzir não funciona:
Set8087CW($27F);
sSQL := 'SET LOCK_TIMEOUT ';
Parece ser mais exótico...Mas por debugbreak em 'Get8087CW = $27F' eu poderia reproduzi-lo em outra string:FPU parte 1:FPU parte 2:
FPU parte 3:
Final da FPU:corrupto!:
Nota 2: Talvez a pilha FPU deva ser limpa no System.Move?
Solução
Não vi esse problema em particular, mas o movimento pode definitivamente ficar confuso se a FPU estiver em um estado ruim. O driver VPN da Cisco pode estragar tudo, mesmo que você não esteja fazendo nada relacionado à rede.
http://brianorr.blogspot.com/2006/11/Intel-Pentium-D-floating-point-unit.html quebrado
https://web.archive.org/web/20160601043520/http://www.dankohn.com/archives/343
http://blog.excastle.com/2007/08/28/delphi-bug-of-the-day-fpu-stack-leak/ (Comentários de Ritchie Annand)
No nosso caso, detectamos o driver VPN de buggy e trocamos de movimento e preenchem com as versões Delphi 7, substituímos o InttoStr por uma versão Pascal (o Int64-Version usa a FPU) e, como estamos usando o Fastmm, desativamos seu personalizado fixo personalizado O tamanho de mover rotinas também, já que são ainda mais suscetíveis que o sistema.
Outras dicas
Pode ser um bug no seu driver de vídeo que não preserva a palavra de controle 8087 quando executa a operação de alongamento.
No passado, vi comportamentos semelhantes ao usar certos drivers de impressora. Eles acham que possuem o 8087 CW e estão errados ...
Observe o valor padrão do 8087 CW em Delphi parece US $ 1372; Para uma explicação mais detalhada dos valores da CW, consulte Este artigo: Isso também explica uma situação que Michael Justin descreveu quando seu 8087CW foi cortado.
--Jeroen
Apenas para suas informações (caso alguém também tenha o mesmo problema): fizemos uma atualização do nosso software para um cliente e a tela sensível ao toque completa travada quando nosso aplicativo foi iniciado! O Windows estava completamente congelado! O PC teve que ser reiniciado (desligado). Demorou algum tempo para descobrir a causa do congelamento completo.
Felizmente, tivemos um (apenas 1!) Stacktrace de um AV em fastmove.Largessemove. Desativei o uso do SSE em FastMove, e o problema se foi.
A propósito: a tela sensível ao toque tem uma CPU via Nehemiah com um chipset S3.
Portanto, você não apenas pode obter corrupções de memória ao usar a FPU, mas também um congelamento completo!
Para quem ainda está interessado nisso:Há ainda outra causa possível de problemas:
Delphi Rio ainda vem com uma versão ASM quebrada do Move
.
Tive o prazer de encontrar esse bug hoje, por sorte tive um caso de teste reproduzível.O problema é este pedaço de código:
* ***** BEGIN LICENSE BLOCK *****
*
* The assembly function Move is licensed under the CodeGear license terms.
*
* The initial developer of the original code is Fastcode
*
* Portions created by the initial developer are Copyright (C) 2002-2004
* the initial developer. All Rights Reserved.
*
* Contributor(s): John O'Harrow
*
* ***** END LICENSE BLOCK ***** *)
// ... some less interesting parts omitted ...
@@LargeMove:
JNG @@LargeDone {Count < 0}
CMP EAX, EDX
JA @@LargeForwardMove
// the following overlap test is broken
// when size>uint(destaddr), EDX underflows to $FFxxxxxx, in which case
// we jump to @LargeForwardMove even if a backward loop would be appropriate
// this will effectively shred everything at EDX + size
SUB EDX, ECX // when this underflows ...
CMP EAX, EDX // ... we also get CF=1 here (EDX is usually < $FFxxxxxx)
LEA EDX, [EDX+ECX] // (does not affect flags)
JNA @@LargeForwardMove // ... CF=1 so let's jump into disaster!
SUB ECX, 8 {Backward Move}
PUSH ECX
FILD QWORD PTR [EAX+ECX] {Last 8}
FILD QWORD PTR [EAX] {First 8}
ADD ECX, EDX
AND ECX, -8 {8-Byte Align Writes}
SUB ECX, EDX