I have come up with the following snippet by building upon the answers given to my StackOverflow question. Just trying to get some other eyeballs to review things so that they can point out any egregious mistakes that I probably made.
One area that was a bit tricky to me to get working is the bottom half of the
readFileHeader label; there seemed to be some conflicting information about the
cmov instruction and I still don't quite understand if I "should've" gone with an alternative that involves a jump or two. Sure, such a change is a micro-optimization but a huge part of this exercise to me is trying to understand the way different constructs affect the instruction pipeline and whatnot.
include WindowsApi.inc ; imports all the "magic" constants that are in the following code include WString.inc ; imports the macro for creating constant unicode strings var0 textequ <rcx> var1 textequ <rdx> var2 textequ <r8> var3 textequ <r9> var4 textequ <qword ptr [(rsp + 20h)]> var5 textequ <qword ptr [(rsp + 28h)]> var6 textequ <qword ptr [(rsp + 30h)]> TRUE = 1h .const align 2 filePath: WString <C:/Temp/Validation.csv> .code Main proc sub rsp, 1048h ; align with 16 while simultaneously making room on the stack for the "home space", some parameters, and a 4096 byte buffer lea var0, filePath ; put address of file path into parameter slot 0 mov var1, FILE_ACCESS_READ ; put access mode into parameter slot 1 mov var2, FILE_SHARE_READ ; put share mode into parameter slot 2 xor var3, var3 ; put security attributes into parameter slot 3 mov var4, FILE_DISPOSITION_OPEN ; put disposition into parameter slot 4 mov var5, FILE_FLAG_NORMAL ; put flags into parameter slot 5 mov var6, WINDOWS_NULL ; put pointer to template handle into parameter slot 6 call CreateFile ; create file handle cmp rax, WINDOWS_INVALID_HANDLE ; validate file handle je exitMain ; skip to exit point if create validation failed mov var5, rax ; save a reference to the file handle for later (taking advantage of the unused parameter slot 5) jmp readFileHeader ; skip to read file header readFileBody: xor eax, eax ; TODO: something useful with the number of bytes read in ecx... readFileHeader: mov var0, var5 ; put file handle into parameter slot 0 lea var1, qword ptr [(rsp + 38h)] ; put pointer to file buffer into parameter slot 1 mov var2, 1000h ; put requested number of bytes to read into parameter slot 2 lea var3, var6 ; put pointer to actual number of bytes that were read into parameter slot 3 (taking advantage of the unused parameter slot 6) mov var4, WINDOWS_NULL ; put overlapped pointer into parameter slot 4 call ReadFile ; read file handle mov rcx, var6 ; put pointer to actual number of bytes that were read into rcx mov edx, TRUE ; assume that body should be processed by storing TRUE in edx test eax, eax ; validate file read operation (non-zero == no errors) cmovz edx, eax ; store zero in edx if file read operation failed test ecx, ecx ; check for end of file (non-zero == more data) cmovz edx, ecx ; store zero in edx if end of file reached test edx, edx ; test edx for zero jne readFileBody ; skip to read file body if edx was not zero readFileFooter: ; TODO: properly handle errors by inspecting the value of eax... mov var0, var5 ; put the reference to the file handle into parameter slot 0 call CloseHandle ; close file handle exitMain: xor ecx, ecx ; set return value to zero call ExitProcess ; return control to Windows Main endp end
Edit; here is the best alternative I have been able to imagine so far; reduces total number of instructions from 9 to 6:
call ReadFile ; read file handle mov ecx, 0FFFFFFFFh ; put max 32-bit value into ecx test eax, eax ; validate file read operation (non-zero == no errors) cmovz ecx, eax ; if file read operation failed, put zero into ecx and rcx, var6 ; if rcx is not zero, put the number of bytes read from var6 into rcx jne readFileBody ; if rcx is not zero, skip to readFileBody