如何在Windows系统下用Go语言实现底层原始磁盘读写?
Got it, let's break down how to work with raw disks on Windows using Go—since you're familiar with the Linux approach and need to replicate functionality similar to that C# MSDN example and the C CreateFile snippet you shared.
Core Concepts
Windows exposes raw physical drives via special device paths:
- For the first physical disk:
\\.\PhysicalDrive0 - Replace the number to target other disks (e.g.,
PhysicalDrive1for the second disk)
To interact with these, we'll use Go's syscall package to invoke Windows API functions directly—just like the C code does. The key APIs we'll use are:
CreateFileW(to get a handle to the disk)SetFilePointerEx(to seek to specific sectors)ReadFile/WriteFile(to read/write raw sector data)
Full Code Example
Here's a complete implementation that can read the MBR (sector 0, 512 bytes) and write to a specified sector. Warning: Writing to raw disks can destroy data—test on a non-production disk only!
package main import ( "fmt" "syscall" "unsafe" ) const ( GENERIC_READ = 0x80000000 GENERIC_WRITE = 0x40000000 FILE_SHARE_READ = 0x00000001 FILE_SHARE_WRITE = 0x00000002 OPEN_EXISTING = 3 INVALID_HANDLE_VALUE = ^syscall.Handle(0) ) // ReadSector reads a single sector from the specified physical disk func ReadSector(diskNumber int, sectorIndex int64, sectorSize uint32) ([]byte, error) { // Build the device path: \\.\PhysicalDriveN devicePath := fmt.Sprintf("\\\\.\\PhysicalDrive%d", diskNumber) pathPtr, err := syscall.UTF16PtrFromString(devicePath) if err != nil { return nil, fmt.Errorf("failed to convert path: %w", err) } // Open the disk handle (requires admin rights) handle, err := syscall.CreateFileW( pathPtr, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0, ) if err != nil || handle == INVALID_HANDLE_VALUE { return nil, fmt.Errorf("failed to open disk: %w", err) } defer syscall.CloseHandle(handle) // Seek to the target sector: offset = sectorIndex * sectorSize var offset int64 = sectorIndex * int64(sectorSize) var newOffset int64 err = syscall.SetFilePointerEx(handle, offset, &newOffset, 0) if err != nil { return nil, fmt.Errorf("failed to seek to sector: %w", err) } // Read the sector data data := make([]byte, sectorSize) var bytesRead uint32 err = syscall.ReadFile(handle, data, &bytesRead, nil) if err != nil || bytesRead != sectorSize { return nil, fmt.Errorf("failed to read sector: %w, bytes read: %d", err, bytesRead) } return data, nil } // WriteSector writes data to a specified sector on the physical disk func WriteSector(diskNumber int, sectorIndex int64, data []byte) error { sectorSize := uint32(len(data)) // Validate sector size (must be a valid sector size for the disk) if sectorSize != 512 && sectorSize != 4096 { return fmt.Errorf("invalid sector size: %d (expected 512 or 4096)", sectorSize) } devicePath := fmt.Sprintf("\\\\.\\PhysicalDrive%d", diskNumber) pathPtr, err := syscall.UTF16PtrFromString(devicePath) if err != nil { return fmt.Errorf("failed to convert path: %w", err) } // Open disk with write access handle, err := syscall.CreateFileW( pathPtr, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0, ) if err != nil || handle == INVALID_HANDLE_VALUE { return fmt.Errorf("failed to open disk for writing: %w", err) } defer syscall.CloseHandle(handle) // Seek to target sector var offset int64 = sectorIndex * int64(sectorSize) var newOffset int64 err = syscall.SetFilePointerEx(handle, offset, &newOffset, 0) if err != nil { return fmt.Errorf("failed to seek to sector: %w", err) } // Write data var bytesWritten uint32 err = syscall.WriteFile(handle, data, &bytesWritten, nil) if err != nil || bytesWritten != sectorSize { return fmt.Errorf("failed to write sector: %w, bytes written: %d", err, bytesWritten) } return nil } func main() { // Example: Read MBR (sector 0, 512 bytes) from PhysicalDrive0 mbrData, err := ReadSector(0, 0, 512) if err != nil { fmt.Printf("Error reading MBR: %v\n", err) return } fmt.Println("MBR first 16 bytes:", mbrData[:16]) // Example: Write to a test sector (UNCOMMENT AT YOUR OWN RISK!) // testData := make([]byte, 512) // copy(testData, []byte("TEST_SECTOR_DATA")) // err = WriteSector(1, 0, testData) // Use a non-system disk! // if err != nil { // fmt.Printf("Error writing sector: %v\n", err) // return // } // fmt.Println("Sector written successfully") }
Key Notes & Warnings
- Admin Rights Required: Your Go program must be run as Administrator. Without this,
CreateFileWwill return an invalid handle with an access denied error. - Sector Size Awareness: While 512 bytes is standard, many modern disks use 4096-byte sectors. To get the exact sector size of your disk, you can call
DeviceIoControlwith theIOCTL_DISK_GET_DRIVE_GEOMETRYcontrol code (adds a bit more complexity, but worth it for robustness). - Data Loss Risk: Writing to raw disks (especially sector 0, the MBR) will corrupt your operating system or data if done on a live disk. Always test on a spare, empty disk.
- API Error Handling: The example includes basic error handling, but you can expand it to check for specific Windows error codes using
syscall.GetLastError().
Mapping to Your C Code
The Go syscall.CreateFileW call directly mirrors your C code:
\\\\.\\PhysicalDrive0→ Same device path (converted to UTF-16 for Windows API)GENERIC_ALL→ We split intoGENERIC_READ|GENERIC_WRITEfor explicit accessFILE_SHARE_READ | FILE_SHARE_WRITE→ Same sharing modeOPEN_EXISTING→ Same creation disposition
内容的提问来源于stack exchange,提问作者legonporto

