Bogdan Botezatu
Bogdan Botezatu

Reputation: 614

Asynchronous ReadFile in Delphi XE2

I'm writing a small PE file analyzer and I have to read the contents of the PE file. I'm doing this via the ReadFile function, as shown below:

function TMainForm.GetPEData(var filename: string) : boolean;
var
  hFile:   DWORD;
  IDH:     TImageDosHeader;
  INH:     TImageNtHeaders;
  ISH:     TImageSectionHeader;
  dwRead: DWORD;
  szBuff: array[0..7] of Char;
  i:      WORD;
  PE: TPEFile;
begin
  Result := False;
  PE := TPeFile.Create;
  if PE.LoadFromFile (filename) then  
    Form2.edEntryPoint.Text := IntToHex(PE.RvaToFileOffset(PE.AddressOfEntryPoint), 8);
  SplashScreen.sLabel1.Caption := 'PE File Loaded';
  hFile := CreateFile(PChar(filename), GENERIC_READ, 
                      FILE_SHARE_WRITE, nil, 
                      OPEN_EXISTING, 0, 0);
  if hFile <> INVALID_HANDLE_VALUE then
  begin
    SetFilePointer(hFile, 0, nil, FILE_BEGIN);
    SplashScreen.sLabel1.Caption := 'Reading DOS File Headers...';
    ReadFile(hFile, IDH, 64, dwRead, nil);
    if IDH.e_magic = IMAGE_DOS_SIGNATURE then
    begin
      SetFilePointer(hFile, IDH._lfanew, nil, FILE_BEGIN);
      SplashScreen.sLabel1.Caption := 'Reading NT File Headers...';
      //Here is where the UI freezes while the file is read...
      ReadFile(hFile, INH, 248, dwRead, nil);
      if INH.Signature = IMAGE_NT_SIGNATURE then
      begin
        Form2.edImageBase.Text := IntToHex(INH.OptionalHeader.ImageBase, 8);
        Form2.edSizeOfImage.Text := IntToHex(INH.OptionalHeader.SizeOfImage, 8);
        Form2.edLinkerVersion.Text := IntToStr(INH.OptionalHeader.MajorLinkerVersion) + '.' + 
              IntToStr(INH.OptionalHeader.MinorLinkerVersion);
        Form2.edFileAlignment.Text := IntToHex(INH.OptionalHeader.FileAlignment, 8);
        Form2.edSectionAlignment.Text := IntToHex(INH.OptionalHeader.SectionAlignment, 8);
        Form2.edSubSystem.Text := IntToHex(INH.OptionalHeader.Subsystem, 4);
        Form2.edEPFilestamp.Text := IntToStr(INH.FileHeader.TimeDateStamp);
        Form2.edFileType.Text := GetPEFileType(PE.ImageNtHeaders.Signature);

        for i := 0 to INH.FileHeader.NumberOfSections - 1 do
        begin
          SetFilePointer(hFile, IDH._lfanew + 248 + i * 40, nil, FILE_BEGIN);
          ReadFile(hFile, ISH, 40, dwRead, nil);
          CopyMemory(@szBuff[0], @ISH.Name[0], 8);

          with Form2.sListView1.Items.Add do
          begin
            Caption := ShortString(szBuff);
            SubItems.Add(IntToHex(ISH.VirtualAddress, 8));
            SubItems.Add(IntToHex(ISH.Misc.VirtualSize, 8));
            SubItems.Add(IntToHex(ISH.PointerToRawData, 8));
            SubItems.Add(IntToHex(ISH.SizeOfRawData, 8));
            SubItems.Add(IntToHex(ISH.Characteristics, 8));
          end;
        end;
      end;
    end;
    CloseHandle(hFile);
    Result := True;
  end;
end;

The bad thing is that, depending on the size of the file, I noticed that the ReadFile would often lag - and it happens synchronously. In the meantime, the UI freezes and looks horribly wrong to the user, who would be tempted to terminate it. I have considered threading, but I just want to see if there is any way I can use ReadFile in asynchronous mode. If there isn't, I'll jump to threading, even if I'll have a lot to modify in my code.

Thank you in advance.

Upvotes: 2

Views: 1375

Answers (2)

opc0de
opc0de

Reputation: 11768

In this cases I always read the whole file to the memory also I use the TFileStream class for easier manipulation.

It is simpler to have the whole file in memory and PE files are usually small.

  type
    TSections = array [0..0] of TImageSectionHeader;
    PSections = ^TSections;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  FS : TFileStream;
  fisier : PImageDosHeader;
  INH : PImageNtHeaders;
  ISH : PSections;
  i   : Word;
begin
  FS := TFileStream.Create('fisierul_tau.exe',fmOpenRead);
  GetMem(fisier,FS.size); //Aloci memorie pentru fisier
  FS.Read(fisier^,FS.Size); // Il citesti;
  FS.Free;
  INH := PImageNtHeaders(DWORD(fisier) + DWORD(fisier^._lfanew));
  ISH := PSections(DWORD(INH) + SizeOf(TImageNtHeaders));
  for i := 0 to INH^.FileHeader.NumberOfSections - 1 do
  begin
      ShowMessage(PAnsiChar(@ISH[i].Name[0]));
  end;
end;

Upvotes: 1

Lawrence Barsanti
Lawrence Barsanti

Reputation: 33342

The ReadFile function reads data from a file, and starts at the position that the file pointer indicates. You can use this function for both synchronous and asynchronous operations.

It is possible to use ReadFile asynchronously but depending on your UI this may not be the best solution. Do you want your users to do anything while they're waiting for the PE file to load?

If you want your users to wait but have confidence that your program didn't freeze you could add a progress bar or just update your SplashScreen.

for i := 0 to INH.FileHeader.NumberOfSections - 1 do
begin
   SplashScreen.sLabel1.Caption := 'Reading section ' + IntToStr(i) + ' of ' + IntToStr(INH.FileHeader.NumberOfSections);
   SplashScreen.sLabel1.Update; // see Ken Whites comment
   // Application.ProcessMessages;
   ...
end

Upvotes: 1

Related Questions