Is it possible to hook into thread termination on Windows? IOW, I would like to be notified if a thread inside the process (not interested in other processes and their threads) has terminated (either normally or - more important - forcefully).
Alternatively, hooking into thread creation would also do.
Rationale: I have a library that manages some information on per-thread basis (think of it as a process-wide per-thread cache for some information). When a thread is terminated I have to remove all thread-specific information from the cache. [Cache associations are implemented using thread ID which may get reused for future threads.]
There's no problem with "normal" execution order as the library user will detach the current thread from the library which will clear the state. Problems start to appear if somebody kills the thread owning cached resource.
The best way is to call
WaitForSingleObject with the HANDLE of the thread (call OpenThread using the thread id to get the HANDLE).
If your program is in a dll, you can set up to handle the DllMain method. This is called when a thread or process starts/ends.
For example,
library MyDLL;
uses
SysUtils, Windows;
procedure DllMain(reason: integer) ;
var
dyingThreadId: Cardinal;
begin
case reason of
DLL_THREAD_DETACH:
begin
dyingThreadId := GetCurrentThreadId();
// handle thread exit with thread id
end;
end;
end;
begin
DllProc := #DllMain;
end.
EDIT: The call is made in the context of the exiting thread, so you can call GetCurrentThreadId() to get the thread's id.
You can use the Win32_ThreadStopTrace WMI event to detect the termination of any thread in the system.
To start monitoring this event you must write a WQLsentence like this
Select * from Win32_ThreadStopTrace Within 1 Where ProcessID=PID_Of_Your_App
check this sample
uses
Classes;
type
TProcWmiEventThreadeCallBack = procedure(AObject: OleVariant) of object;
TWmiEventThread = class(TThread)
private
Success : HResult;
FSWbemLocator: OleVariant;
FWMIService : OleVariant;
FEventSource : OleVariant;
FWbemObject : OleVariant;
FCallBack : TProcWmiEventThreadeCallBack;
FWQL : string;
FServer : string;
FUser : string;
FPassword : string;
FNameSpace : string;
TimeoutMs : Integer;
procedure RunCallBack;
public
Constructor Create(CallBack : TProcWmiEventThreadeCallBack;const Server,User,PassWord,NameSpace,WQL:string;iTimeoutMs : Integer); overload;
destructor Destroy; override;
procedure Execute; override;
end;
implementation
uses
SysUtils,
ComObj,
Variants,
ActiveX;
constructor TWmiEventThread.Create(CallBack : TProcWmiEventThreadeCallBack;const Server,User,PassWord,NameSpace,WQL:string;iTimeoutMs : Integer);
begin
inherited Create(False);
FreeOnTerminate := True;
FCallBack := CallBack;
FWQL := WQL;
FServer := Server;
FUser := User;
FPassword := PassWord;
FNameSpace := NameSpace;
TimeoutMs := iTimeoutMs;
end;
destructor TWmiEventThread.Destroy;
begin
FSWbemLocator:=Unassigned;
FWMIService :=Unassigned;
FEventSource :=Unassigned;
FWbemObject :=Unassigned;
inherited;
end;
procedure TWmiEventThread.Execute;
const
wbemErrTimedout = $80043001;
begin
Success := CoInitialize(nil); //CoInitializeEx(nil, COINIT_MULTITHREADED);
try
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
FWMIService := FSWbemLocator.ConnectServer(FServer, FNameSpace, FUser, FPassword);
FEventSource := FWMIService.ExecNotificationQuery(FWQL);
while not Terminated do
begin
try
FWbemObject := FEventSource.NextEvent(TimeoutMs); //set the max time to wait (ms)
except
on E:EOleException do
if EOleException(E).ErrorCode=HRESULT(wbemErrTimedout) then //Check for the timeout exception and ignore if exist
FWbemObject:=Null
else
raise;
end;
if FindVarData(FWbemObject)^.VType <> varNull then
Synchronize(RunCallBack);
FWbemObject:=Unassigned;
end;
finally
case Success of
S_OK, S_FALSE: CoUninitialize;
end;
end;
end;
procedure TWmiEventThread.RunCallBack;
begin
FCallBack(FWbemObject);
end;
Now to use this thread in your app you must call it in this way
WmiThread:=TWmiEventThread.Create(
Log,
'.',
'',
'',
'root\cimv2',
Format('Select * from Win32_ThreadStopTrace Within 1 Where ProcessID=%d',[GetCurrentProcessId]),1);
and in the callback function
procedure TForm1.Log(AObject: OleVariant);
begin
{
The OleVariant parameter has these properties
uint32 ProcessID;
uint8 SECURITY_DESCRIPTOR[];
uint32 ThreadID;
uint64 TIME_CREATED;
}
//do your stuff here
Memo1.Lines.Add(Format('Thread %s terminated ',[AObject.ThreadID]));
end;
You could use something like Detours to do API-level hooking of Win32 APIs like TerminateThread.
I'm not seeing why you need to do this, though. It sounds like you need to clear the thread's associated cache when the thread dies so you can re-use that slot if another thread with the same ID comes along. Is this correct?
If so, couldn't you just clear the cache association in DllMain when you get the DLL_THREAD_ATTACH event? This is essentially your new thread notification. At this point, you know you have a new thread, so isn't it safe to clear the existing associated cache?
The other alternative that might work is thread-local storage (TLS). You can use Win32 APIs like TlsAlloc/TlsSetValue to store thread-specific information. You could also define a variable with __declspec(thread) to have the compiler manage the TLS for you. This way, each thread maintains its own cache. The code remains the same for each thread, but the data accesses are relative to the thread.
program ThreadExitHook;
{$APPTYPE CONSOLE}
uses
Windows,
Classes,
madCodeHook;
type
TLdrShutdownThread = procedure; stdcall;
var
LdrShutdownThreadNext : TLdrShutdownThread;
procedure LdrShutdownThreadCallback; stdcall;
begin
WriteLn('Thread terminating:', GetCurrentThreadId);
LdrShutdownThreadNext;
end;
begin
HookAPI('ntdll.dll', 'LdrShutdownThread', #LdrShutdownThreadCallback, #LdrShutdownThreadNext);
TThread.CreateAnonymousThread(procedure begin
WriteLn('Hello from Thread');
Sleep(1000);
end).Start;
ReadLn;
UnhookAPI(#LdrShutdownThreadNext);
end.
Here is a version that does not depend on any external library:
program Project7;
{$APPTYPE CONSOLE}
uses
Windows,
Classes;
{==============================================================================}
function IsWin9x: Boolean;
asm
MOV EAX, FS:[030H]
TEST EAX, EAX
SETS AL
end;
{------------------------------------------------------------------------------}
function CalcJump(Src, Dest: DWORD): DWORD;
begin
if (Dest < Src) then begin
Result := Src - Dest;
Result := $FFFFFFFF - Result;
Result := Result - 4;
end else begin
Result := Dest - Src;
Result := Result - 5;
end;
end;
{------------------------------------------------------------------------------}
function OpCodeLength(Address: DWORD): DWORD; cdecl; assembler;
const
O_UNIQUE = 0;
O_PREFIX = 1;
O_IMM8 = 2;
O_IMM16 = 3;
O_IMM24 = 4;
O_IMM32 = 5;
O_IMM48 = 6;
O_MODRM = 7;
O_MODRM8 = 8;
O_MODRM32 = 9;
O_EXTENDED = 10;
O_WEIRD = 11;
O_ERROR = 12;
asm
pushad
cld
xor edx, edx
mov esi, Address
mov ebp, esp
push 1097F71Ch
push 0F71C6780h
push 17389718h
push 101CB718h
push 17302C17h
push 18173017h
push 0F715F547h
push 4C103748h
push 272CE7F7h
push 0F7AC6087h
push 1C121C52h
push 7C10871Ch
push 201C701Ch
push 4767602Bh
push 20211011h
push 40121625h
push 82872022h
push 47201220h
push 13101419h
push 18271013h
push 28858260h
push 15124045h
push 5016A0C7h
push 28191812h
push 0F2401812h
push 19154127h
push 50F0F011h
mov ecx, 15124710h
push ecx
push 11151247h
push 10111512h
push 47101115h
mov eax, 12472015h
push eax
push eax
push 12471A10h
add cl, 10h
push ecx
sub cl, 20h
push ecx
xor ecx, ecx
dec ecx
##ps:
inc ecx
mov edi, esp
##go:
lodsb
mov bh, al
##ft:
mov ah, [edi]
inc edi
shr ah, 4
sub al, ah
jnc ##ft
mov al, [edi-1]
and al, 0Fh
cmp al, O_ERROR
jnz ##i7
pop edx
not edx
##i7:
inc edx
cmp al, O_UNIQUE
jz ##t_exit
cmp al, O_PREFIX
jz ##ps
add edi, 51h
cmp al, O_EXTENDED
jz ##go
mov edi, [ebp+((1+8)*4)+4]
##i6:
inc edx
cmp al, O_IMM8
jz ##t_exit
cmp al, O_MODRM
jz ##t_modrm
cmp al, O_WEIRD
jz ##t_weird
##i5:
inc edx
cmp al, O_IMM16
jz ##t_exit
cmp al, O_MODRM8
jz ##t_modrm
##i4:
inc edx
cmp al, O_IMM24
jz ##t_exit
##i3:
inc edx
##i2:
inc edx
pushad
mov al, 66h
repnz scasb
popad
jnz ##c32
##d2:
dec edx
dec edx
##c32:
cmp al, O_MODRM32
jz ##t_modrm
sub al, O_IMM32
jz ##t_imm32
##i1:
inc edx
##t_exit:
jmp ##ASMEnded
##t_modrm:
lodsb
mov ah, al
shr al, 7
jb ##prmk
jz ##prm
add dl, 4
pushad
mov al, 67h
repnz scasb
popad
jnz ##prm
##d3: sub dl, 3
dec al
##prmk:jnz ##t_exit
inc edx
inc eax
##prm:
and ah, 00000111b
pushad
mov al, 67h
repnz scasb
popad
jz ##prm67chk
cmp ah, 04h
jz ##prmsib
cmp ah, 05h
jnz ##t_exit
##prm5chk:
dec al
jz ##t_exit
##i42: add dl, 4
jmp ##t_exit
##prm67chk:
cmp ax, 0600h
jnz ##t_exit
inc edx
jmp ##i1
##prmsib:
cmp al, 00h
jnz ##i1
lodsb
and al, 00000111b
sub al, 05h
jnz ##i1
inc edx
jmp ##i42
##t_weird:
test byte ptr [esi], 00111000b
jnz ##t_modrm
mov al, O_MODRM8
shr bh, 1
adc al, 0
jmp ##i5
##t_imm32:
sub bh, 0A0h
cmp bh, 04h
jae ##d2
pushad
mov al, 67h
repnz scasb
popad
jnz ##chk66t
##d4: dec edx
dec edx
##chk66t:
pushad
mov al, 66h
repnz scasb
popad
jz ##i1
jnz ##d2
##ASMEnded:
mov esp, ebp
mov [result+(9*4)], edx
popad
end;
{------------------------------------------------------------------------------}
function ApiHook(ModName, ApiName: PChar; FuncAddr, HookedApi: Pointer; var MainApi: Pointer): Boolean;
var
dwCount, Cnt, i, jmp: DWORD;
P: Pointer;
hMod, OldP, TMP: Cardinal;
begin
Result := False;
if IsWin9x then
Exit;
P := FuncAddr;
if P = nil then begin
hMod := GetModuleHandle(ModName);
if hMod = 0 then
hMod := LoadLibrary(ModName);
P := GetProcAddress(hMod, ApiName);
end;
if (P = nil) or (HookedApi = nil) then
Exit;
if not VirtualProtect(P, $40, PAGE_EXECUTE_READWRITE, #OldP) then
Exit;
if ((Byte(P^) = $68) and (DWORD(Pointer(DWORD(P) + 1)^) = DWORD(HookedApi))) then
Exit;
MainApi := VirtualAlloc(nil, $1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if MainApi = nil then
Exit;
Cnt := 0;
for dwCount := 0 to $3F do begin
Inc(Cnt, OpCodeLength(DWORD(P) + Cnt));
for i := 0 to Cnt - 1 do
PByte(MainApi)[i] := PByte(P)[i];
if Cnt > 5 then
Break;
end;
PByte(MainApi)[Cnt] := $68;
DWORD(Pointer(DWORD(MainApi) + Cnt + 1)^) := DWORD(P) + Cnt;
PByte(MainApi)[Cnt + 5] := $C3;
PByte(MainApi)[Cnt + 6] := $99;
if (OpCodeLength(DWORD(MainApi)) = 5) and
((Byte(MainApi^) = $E8) or (Byte(MainApi^) = $E9)) then
begin
jmp := DWORD(P) + DWORD(Pointer(DWORD(MainApi) + 1)^) + 5;
DWORD(Pointer(DWORD(MainApi) + 1)^) := CalcJump(DWORD(MainApi), jmp);
end;
PByte(P)[0] := $68;
DWORD(Pointer(DWORD(P) + 1)^) := DWORD(HookedApi);
PByte(P)[5] := $C3;
VirtualProtect(P, $40, OldP, #TMP);
Result := True;
end;
{------------------------------------------------------------------------------}
function ApiUnHook(ModName, ApiName: PChar; FuncAddr, HookedApi: Pointer; var MainApi: Pointer): Boolean;
var
dwCount, Cnt, i, jmp: DWORD;
P: Pointer;
hMod, OldP, TMP: Cardinal;
begin
Result := False;
if IsWin9x then
Exit;
P := FuncAddr;
if P = nil then begin
hMod := GetModuleHandle(ModName);
P := GetProcAddress(hMod, ApiName);
end;
if (P = nil) or (MainApi = nil) or (HookedApi = nil) then
Exit;
if not VirtualProtect(P, $40, PAGE_EXECUTE_READWRITE, #OldP) then
Exit;
if ((Byte(P^) <> $68) or (DWORD(Pointer(DWORD(P) + 1)^) <> DWORD(HookedApi))) then
Exit;
Cnt := 0;
for dwCount := 0 to $3F do begin
Inc(Cnt, OpCodeLength(DWORD(MainApi) + Cnt));
if (Byte(Pointer(DWORD(MainApi) + Cnt)^) = $C3) and
(Byte(Pointer(DWORD(MainApi) + Cnt + 1)^) = $99) then
Break;
for i := 0 to Cnt - 1 do
PByte(P)[i] := PByte(MainApi)[i];
end;
if (OpCodeLength(DWORD(P)) = 5) and ((Byte(P^) = $E8) or (byte(P^) = $E9)) then begin
jmp := DWORD(MainApi) + DWORD(Pointer(DWORD(MainApi) + 1)^) + 5;
DWORD(Pointer(DWORD(P) + 1)^) := CalcJump(DWORD(P), jmp);
end;
VirtualProtect(P, $40, OldP, #TMP);
VirtualFree(MainApi, 0, MEM_RELEASE);
Result := True;
end;
{==============================================================================}
type
TLdrShutdownThread = procedure; stdcall;
var
LdrShutdownThreadNext : TLdrShutdownThread;
procedure LdrShutdownThreadCallback; stdcall;
begin
WriteLn('Thread terminating:', GetCurrentThreadId);
LdrShutdownThreadNext;
end;
begin
ApiHook('ntdll.dll', 'LdrShutdownThread', nil, #LdrShutdownThreadCallback, #LdrShutdownThreadNext);
TThread.CreateAnonymousThread(procedure begin
WriteLn('Hello from Thread');
Sleep(1000);
WriteLn('Waking up');
end).Start;
ReadLn;
ApiUnHook('ntdll.dll', 'LdrShutdownThread', nil, #LdrShutdownThreadCallback, #LdrShutdownThreadNext);
TThread.CreateAnonymousThread(procedure begin
WriteLn('Hello from Thread');
Sleep(1000);
WriteLn('Waking up');
end).Start;
ReadLn;
end.
Chris' mention of DLL_THREAD_ATTACH gave me an idea ...
Basically, associating cache with thread ID is a bad thing. I have to rework my library so that a thread will initially establish some kind of handle and then manage associations using this handle.
I guess if you really want to badly enough, you could use the debugging API (e.g., WaitForDebugEvent, ContinueDebugEvent), . You'll get an EXIT_THREAD_DEBUG_EVENT when a thread exits.
I can't say that's exactly a straightforward or clean way to do it, but if you can't come up with anything else, it's probably better than nothing.
Boost provides boost::this_thread::at_thread_exit() which allows you to provide arbitrary code to run when the current thread exits. If you call this on each thread then when it exits normally the code will be run. If a thread is terminated forcibly with TerminateThread then no more code is run on that thread, so the at_thread_exit functions are not called. The only way to handle such cases would be to hook TerminateThread, though this won't necessarily handle the case that another process terminates your threads.
The only way to reliably do this is in a DLL that hooks DLL_THREAD_ATTACH and DLL_THREAD_DETACH. See previous discussion here.
Related
I have to convert this java program into x86 assembly language. I have already tried and coded the whole thing but my program would just get stuck in an infinite loop, or it would end wrong.
public static void main(String[] args)
{
int sum = 0;
int i = 0;
int j = 12;
int var1 = 3;
int var2 = 3;
int var3 = 0;
for(i=0; i<j; i++)
{
if (var1 > var3)
{
var1 = var1-i;
}
else
{
var3 = var3+ i;
}
sum = var1 + var2+ var3;
j = j -1;
}
}
What I have so far
.386
.model flat, stdcall
.stack 4096
ExitProcess PROTO, dwExitCode: DWORD
.data
sum SDWORD 0
i SDWORD 0
j SDWORD 12
var1 SDWORD 3
var2 SDWORD 3
var3 SDWORD 0
.code
main PROC
mov esi, i ; esi = 0
mov eax, j ; eax = 12
mov ebx, var1 ; ebx = 3
mov ecx, var2 ; ecx = 3
mov edx, var3 ; edx = 0
for_loop:
cmp esi, eax ; compare esi and eax
inc esi ; increase esi
jb begin_if ; if esi < eax (i < j) jump to begin_if
jmp end_loop ; if not, jump to end_loop
begin_if:
cmp ebx, edx ; compare ebx and edx
ja if_block ; if ebx > edx (var1 > var3) jump to if_block
jmp else_block ; if not, jump to else_block
if_block:
sub ebx, esi ; subtract esi from ebx which is i from var1
jmp end_for ; jump to end_loop
else_block:
add edx, esi ; add esi to edx which is i + var3
jmp end_for ; jump to end_loop
end_for:
add ecx, edx ; add edx to ecx which is var3 + var2 and store it in ecx
add ebx, ecx ; then add ecx to ebx which is varr2+var3+var1
mov sum, ebx ; then store ebx into sum
sub eax, 1 ; subtract 1 from ebx which is j - 1
jmp for_loop ; repeat the loop
end_loop: ; end of the loop
INVOKE ExitProcess, 0
main ENDP
END main
I have trouble because the sum needs to be 15, but my code ended when the sum is only 6.
I would like to use a Label or a Memo to view information:
function GetCPUSpeed: Double;
const
DelayTime = 500;
var
TimerHi, TimerLo: DWORD;
PriorityClass, Priority: Integer;
begin
PriorityClass := GetPriorityClass(GetCurrentProcess);
Priority := GetThreadPriority(GetCurrentThread);
SetPriorityClass(GetCurrentProcess, REALTIME_PRIORITY_CLASS);
SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_TIME_CRITICAL);
Sleep(10);
asm
dw 310Fh
mov TimerLo, eax
mov TimerHi, edx
end;
Sleep(DelayTime);
asm
dw 310Fh
sub eax, TimerLo
sbb edx, TimerHi
mov TimerLo, eax
mov TimerHi, edx
end;
SetThreadPriority(GetCurrentThread, Priority);
SetPriorityClass(GetCurrentProcess, PriorityClass);
Result := TimerLo / (1000 * DelayTime);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage(Format('Your CPU speed: %f MHz', [GetCPUSpeed]));
Label1.caption := GetCPUSpeed;
Memo1.Lines := GetCPUSpeed;
end;
To use the Label or a Memo, I have to convert data into a string. How can I do that?
[DCC Error] speed_cpu.pas(62): E2010 Incompatible types: 'string' and 'Double'
First off, you can't assign a string to the TMemo.Lines property, which is a TStrings object. You would have to assign to either TMemo.Lines.Text or TMemo.Text instead.
As for the actual conversion, you already know one way to do it, via SysUtils.Format():
uses
..., SysUtils;
var
Speed: Double;
...
Speed := GetCPUSpeed;
ShowMessage(Format('Your CPU speed: %f MHz', [Speed]));
Label1.Caption := Format('%f', [Speed]);
Memo1.Text := Format('%f', [Speed]);
You can also use SysUtils.FloatToStr():
uses
..., SysUtils;
var
Speed: Double;
...
Speed := GetCPUSpeed;
ShowMessage(Format('Your CPU speed: %f MHz', [Speed]));
Label1.Caption := FloatToStr(Speed);
Memo1.Text := FloatToStr(Speed);
In XE4+, you can also use SysUtils.TDoubleHelper.ToString():
uses
..., SysUtils;
var
Speed: Double;
...
Speed := GetCPUSpeed;
ShowMessage(Format('Your CPU speed: %f MHz', [Speed]));
Label1.Caption := Speed.ToString;
Memo1.Text := Speed.ToString;
Thank you problem solved.
function Tipo_cpu: string;
var
aVendor: array[0..2] of DWord;
iI, iJ : Integer;
begin
asm
push ebx
xor eax, eax
dw $A20F // CPUID instruction
mov DWord ptr aVendor, ebx
mov DWord ptr aVendor[+4], edx
mov DWord ptr aVendor[+8], ecx
pop ebx
end;
for iI := 0 to 2 do
for iJ := 0 to 3 do
Result := Result + Chr((aVendor[iI] and ($000000FF shl (iJ * 8))) shr (iJ * 8));
end;
function GetCPUSpeed: Double;
const
DelayTime = 500;
var
TimerHi, TimerLo: DWORD;
PriorityClass, Priority: Integer;
begin
PriorityClass := GetPriorityClass(GetCurrentProcess);
Priority := GetThreadPriority(GetCurrentThread);
SetPriorityClass(GetCurrentProcess, REALTIME_PRIORITY_CLASS);
SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_TIME_CRITICAL);
Sleep(10);
asm
dw 310Fh
mov TimerLo, eax
mov TimerHi, edx
end;
Sleep(DelayTime);
asm
dw 310Fh
sub eax, TimerLo
sbb edx, TimerHi
mov TimerLo, eax
mov TimerHi, edx
end;
SetThreadPriority(GetCurrentThread, Priority);
SetPriorityClass(GetCurrentProcess, PriorityClass);
Result := TimerLo / (1000 * DelayTime);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
ids: TidIpWatch;
Speed: Double;
begin
ids := TidIpWatch.Create;
Speed := GetCPUSpeed;
Memo1.Text := 'IP:' + (ids.LocalIP);
Memo1.Text := (Tipo_cpu);
Memo1.Text := Format('%f', [Speed]);
ids.Free;
end;
I have coded in VS2019 using VC++ and compiled using the Intel C++ compiler, a 64 bit command line music file player to play WAV files using WASAPI. The OS is Win 7-SP1.
This is the part of code on which I have questions and need help on. Declaration of variables are left out for conciseness.
// activate an IAudioClient
IAudioClient* pAudioClient;
...
...
// create an event
HANDLE hNeedDataEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
// set the event handle
hr = pAudioClient->SetEventHandle(hNeedDataEvent);
...
...
//works fine
do
{
WaitForSingleObject(hNeedDataEvent, INFINITE);
hr = pAudioRenderClient->ReleaseBuffer(nFramesInBuffer, 0);
hr = pAudioRenderClient->GetBuffer(nFramesInBuffer, &pData);
memcpy(pData, sound_buffer + nBytesToSkip, nBytesThisPass);
nBytesToSkip += nBytesThisPass;
} while (--nBuffersPlayed);
I want to replace the line of code: WaitForSingleObject(hNeedDataEvent, INFINITE);
with inline Assembly code using a syscall. Portability is unimportant since this is just for experimentation/learning because have no knowledge of Assembler.
I found a syscall table for Win7-SP1 on Github and here's what it says for NtWaitForSingleObject:
; ULONG64 __stdcall NtWaitForSingleObject( ULONG64 arg_01 , ULONG64 arg_02 , ULONG64 arg_03 );
NtWaitForSingleObject PROC STDCALL
mov r10 , rcx
mov eax , 1
;syscall
db 0Fh , 05h
ret
NtWaitForSingleObject ENDP
I think the inline Assembly code to replace the call to WaitForSingleObject should be:
__asm
{
mov r10, ?????? ; pHandle
xor edx, edx ; FALSE: The alert cannot be delivered
xor r8d, r8d ; Time-out interval, in microseconds. NULL means infinite
mov eax, 1 ; code number for WaitForSingleObject
syscall
}
My questions are:
What exactly do I need to move to r10 so that it will contain the "handle" of the event?
Is the rest of the inline Assembly code correct?
As an aside, when I disassembled my compiled code I see this:
mov rcx, [rbp+220h+hHandle] ; hHandle
mov edx, 0FFFFFFFFh ; dwMilliseconds
call cs:WaitForSingleObject
__asm
{
mov r10, hNeedDataEvent ; pHandle
xor edx, edx ; FALSE: The alert cannot be delivered
xor r8d, r8d ; Time-out interval, in microseconds. NULL means infinite
mov eax, 1 ; code number for WaitForSingleObject syscall
}
I just assigned the value of hNeedDataEvent to r10 and it worked.
I want to call a function that will perform upper to lower case conversion to a user typed string, preserving the especial characters. This part works, but only for the first 4 characters, everything after that just gets truncated. I believe it is because I have defined the parameters as DWORD:
I have tried using PAGE, PARA and BYTE. The first two don't work and with byte says type missmatch.
upperToLower proc, source:dword, auxtarget:dword
mov eax, source ;Point to string
mov ebx, auxtarget ; point to destination
L1:
mov dl, [eax] ; Get a character from buffer
cmp byte ptr [eax], 0 ; End of string? (not counters)
je printString ; if true, jump to printString
cmp dl, 65 ; 65 == 'A'
jl notUpper ; if less, it's not uppercase
cmp dl, 90 ; 90 == 'Z'
jg notUpper ; if greater, it's not uppercase
xor dl, 100000b ; XOR to change upper to lower
mov [ebx], dl ; add char to target
inc eax ; Move counter up
inc ebx ; move counter up
jmp L1 ; loop
notUpper: ; not uppercase
mov [ebx], dl ; copy the letter
inc eax ;next letter
inc ebx
jmp L1
printString:
invoke WriteConsoleA, consoleOutHandle, auxtarget, sizeof auxtarget, bytesWritten, 0
ret
upperToLower endp
The PROTO:
upperToLower PROTO,
source: dword,
auxtarget: dword
Invoke:
invoke upperToLower, offset buffer, offset target
The buffer parameter is: buffer db 128 DUP(?)
How can I get printed the whole string, and not just the first 4 characters?
Why are only 4 characters being printed? You write the string to the console with:
invoke WriteConsoleA, consoleOutHandle, auxtarget, sizeof auxtarget, bytesWritten, 0
The sizeof auxtarget parameter is the size of auxtarget which is a DWORD (4 bytes) thus you are asking to only print 4 bytes. You need to pass the length of the string. You can easily do so by taking the ending address in EAX and subtracting the source pointer from it. The result would be the length of the string you traversed.
Modify the code to be:
printString:
sub eax, source
invoke WriteConsoleA, consoleOutHandle, auxtarget, eax, bytesWritten, 0
A version of your code that follows the C call convention, uses both a source and destination buffer, tests for the pointers to make sure they aren't NULL, does the conversion using a similar method described by Peter Cordes is as follows:
upperToLower proc uses edi esi, source:dword, dest:dword
; uses ESI EDI is used to tell assembler we are clobbering two of
; the cdecl calling convetions non-volatile registers. See:
; https://en.wikipedia.org/wiki/X86_calling_conventions#cdecl
mov esi, source ; ESI = Pointer to string
test esi, esi ; Is source a NULL pointer?
jz done ; If it is then we are done
mov edi, dest ; EDI = Pointer to string
test edi, edi ; Is dest a NULL pointer?
jz done ; If it is then we are done
xor edx, edx ; EDX = 0 = current character index into the strings
jmp getnextchar ; Jump into loop at point of getting next character
charloop:
lea ecx, [eax - 'A'] ; cl = al-'A', and we do not care about the rest
; of the register
cmp cl, 25 ; if(c >= 'A' && c <= 'Z') c += 0x20;
lea ecx, [eax + 20h] ; without affecting flags
cmovna eax, ecx ; take the +0x20 version if it was in the
; uppercase range to start with
mov [edi + edx], al ; Update character in destination string
inc edx ; Go to next character
getnextchar:
movzx eax, byte ptr [esi + edx]
; mov al, [esi + edx] leaving high garbage in EAX is ok
; too, but this avoids a partial-register stall
; when doing the mov+sub
; in one instruction with LEA
test eax, eax ; Is the character NUL(0) terminator?
jnz charloop ; If not go back and process character
printString:
; EDI = source, EDX = length of string
invoke WriteConsoleA, consoleOutHandle, edi, edx, bytesWritten, 0
mov edx, sizeof buffer
done:
ret
upperToLower endp
A version that takes one parameter and changes the source string to upper case could be done this way:
upperToLower proc, source:dword
mov edx, source ; EDX = Pointer to string
test edx, edx ; Is it a NULL pointer?
jz done ; If it is then we are done
jmp getnextchar ; Jump into loop at point of getting next character
charloop:
lea ecx, [eax - 'A'] ; cl = al-'A', and we do not care about the rest
; of the register
cmp cl, 25 ; if(c >= 'A' && c <= 'Z') c += 0x20;
lea ecx, [eax + 20h] ; without affecting flags
cmovna eax, ecx ; take the +0x20 version if it was in the
; uppercase range to start with
mov [edx], al ; Update character in string
inc edx ; Go to next character
getnextchar:
movzx eax, byte ptr [edx] ; mov al, [edx] leaving high garbage in EAX is ok, too,
; but this avoids a partial-register stall
; when doing the mov+sub in one instruction with LEA
test eax, eax ; Is the character NUL(0) terminator?
jnz charloop ; If not go back and process character
printString:
sub edx, source ; EDX-source=length
invoke WriteConsoleA, consoleOutHandle, source, edx, bytesWritten, 0
done:
ret
upperToLower endp
Observations
A generic upperToLower function that does the string conversion would normally not do the printing itself. You'd normally call upperToLower to do the conversion only, then you'd output the string to the display in a separate call.
I am pretty new to assembly that I'm learning from the last 7 hours (It's an early peek into the courses I had in the next semester starting next month). I read some online tutorials, and the nasm manual and started to port a C program to nasm, just for learning.
int fact(int n)
{
return (n < 0) ? 1 : n * fact(n - 1);
}
I then started to port it to assembly, and had this as my solution:
fact:
; int fact(int n)
cmp dword ebx, 0 ; n == 0
je .yes
.no:
push ebx ; save ebx in stack
sub ebx, dword 1 ; sub 1 from ebx. (n - 1)
call fact ; call fact recursively
pop ebx ; get back the ebx from stack
imul eax, ebx ; eax *= ebx; eax == fact(n - 1)
ret
.yes:
mov eax, dword 1 ; store 1 in eax to return it
ret
I take in a DWORD (int I suppose) in the ebx register and return the value in the eax register. As you can see I am not at all using the variable i that I have declared in the .bss section. My variables are like this:
section .bss
; int i, f
i resb 2
f resb 2
It's 2 bytes for an int right? Okay then I'm prompting the user in the _main, getting the input with _scanf and then calling the function. Other than this and calling the function, I have no other code that changes the value of i variable.
mov ebx, dword [i] ; check for validity of the factorial value
cmp dword ebx, 0
jnl .no
.yes:
push em ; print error message and exit
call _printf
add esp, 4
ret
.no:
push dword 0 ; print the result and exit
push dword [i]
push rm
call _printf
add esp, 12
call fact ; call the fact function
mov dword [f], eax
push dword [f] ; print the result and exit
push dword [i]
push rm
call _printf
add esp, 12
ret
I don't see where I'm modifying the value of i variable, on first print before the call to fact it is indeed the same value entered by the user, but after calling the function, in the later print, it is printing some garbage value, as the following output:
E:\ASM> factorial
Enter a number: 5
The factorial of 5 is 0The factorial of 7864325 is 120
E:\ASM>
Any clues? My complete source code is in this gist: https://gist.github.com/sriharshachilakapati/70049a778e12d8edd9c7acf6c2d44c33