天天看點

實戰DeviceIoControl 之四:擷取硬碟的詳細資訊

q 用ioctl_disk_get_drive_geometry或ioctl_storage_get_media_types_ex隻能得到很少的磁盤參數,我想獲得包括硬碟序列号在内的更加詳細的資訊,有什麼辦法呀?

a 确實,用你所說的i/o控制碼,隻能得到最基本的磁盤參數。擷取磁盤出廠資訊的i/o控制碼,微軟在vc/mfc環境中沒有開放,在ddk中可以發現一些線索。早先,lynn mcguire寫了一個很出名的擷取ide硬碟詳細資訊的程式diskid32,下面的例子是在其基礎上經過增删和改進而成的。

本例中,我們要用到ata/apapi的identify device指令。ata/apapi是國際組織t13起草和釋出的ide/eide/udma硬碟及其它可移動儲存設備與主機接口的标準,至今已經到了ata/apapi-7版本。該接口标準規定了ata/atapi裝置的輸入輸出寄存器和指令集。欲了解更詳細的ata/atapi技術資料,可通路t13的站點。

用到的常量及資料結構有以下一些:

// ioctl控制碼

// #define  dfp_send_drive_command   0x0007c084

#define  dfp_send_drive_command   ctl_code(ioctl_disk_base, 0x0021, method_buffered, file_read_access | file_write_access)

// #define  dfp_receive_drive_data   0x0007c088

#define  dfp_receive_drive_data   ctl_code(ioctl_disk_base, 0x0022, method_buffered, file_read_access | file_write_access)

#define  file_device_scsi           0x0000001b

#define  ioctl_scsi_miniport_identify      ((file_device_scsi << 16) + 0x0501)

#define  ioctl_scsi_miniport        0x0004d008          //  see ntddscsi.h for definition

// ata/atapi指令

#define  ide_ata_identify           0xec     // ata的id指令(identify device)

// ide指令寄存器

typedef struct _ideregs

{

    byte bfeaturesreg;       // 特征寄存器(用于smart指令)

    byte bsectorcountreg;    // 扇區數目寄存器

    byte bsectornumberreg;   // 開始扇區寄存器

    byte bcyllowreg;         // 開始柱面低位元組寄存器

    byte bcylhighreg;        // 開始柱面高位元組寄存器

    byte bdriveheadreg;      // 驅動器/磁頭寄存器

    byte bcommandreg;        // 指令寄存器

    byte breserved;          // 保留

} ideregs, *pideregs, *lpideregs;

// 從驅動程式傳回的狀态

typedef struct _driverstatus

    byte bdrivererror;      // 錯誤碼

    byte bidestatus;        // ide狀态寄存器

    byte breserved[2];      // 保留

    dword dwreserved[2];    // 保留

} driverstatus, *pdriverstatus, *lpdriverstatus;

// ide裝置ioctl輸入資料結構

typedef struct _sendcmdinparams

    dword cbuffersize;      // 緩沖區位元組數

    ideregs irdriveregs;    // ide寄存器組

    byte bdrivenumber;      // 驅動器号

    byte breserved[3];      // 保留

    dword dwreserved[4];    // 保留

    byte bbuffer[1];        // 輸入緩沖區(此處象征性地包含1位元組)

} sendcmdinparams, *psendcmdinparams, *lpsendcmdinparams;

// ide裝置ioctl輸出資料結構

typedef struct _sendcmdoutparams

    dword cbuffersize;          // 緩沖區位元組數

    driverstatus driverstatus;  // 驅動程式傳回狀态

    byte bbuffer[1];            // 輸入緩沖區(此處象征性地包含1位元組)

} sendcmdoutparams, *psendcmdoutparams, *lpsendcmdoutparams;

// ide的id指令傳回的資料

// 共512位元組(256個word),這裡僅定義了一些感興趣的項(基本上依據ata/atapi-4)

typedef struct _idinfo

    ushort  wgenconfig;                 // word 0: 基本資訊字

    ushort  wnumcyls;                   // word 1: 柱面數

    ushort  wreserved2;                 // word 2: 保留

    ushort  wnumheads;                  // word 3: 磁頭數

    ushort  wreserved4;                 // word 4: 保留

    ushort  wreserved5;                 // word 5: 保留

    ushort  wnumsectorspertrack;        // word 6: 每磁道扇區數

    ushort  wvendorunique[3];           // word 7-9: 廠家設定值

    char    sserialnumber[20];          // word 10-19:序列号

    ushort  wbuffertype;                // word 20: 緩沖類型

    ushort  wbuffersize;                // word 21: 緩沖大小

    ushort  weccsize;                   // word 22: ecc校驗大小

    char    sfirmwarerev[8];            // word 23-26: 固件版本

    char    smodelnumber[40];           // word 27-46: 内部型号

    ushort  wmorevendorunique;          // word 47: 廠家設定值

    ushort  wreserved48;                // word 48: 保留

    struct {

        ushort  reserved1:8;

        ushort  dma:1;                  // 1=支援dma

        ushort  lba:1;                  // 1=支援lba

        ushort  disiordy:1;             // 1=可不使用iordy

        ushort  iordy:1;                // 1=支援iordy

        ushort  softreset:1;            // 1=需要ata軟啟動

        ushort  overlap:1;              // 1=支援重疊操作

        ushort  queue:1;                // 1=支援指令隊列

        ushort  inldma:1;               // 1=支援交叉存取dma

    } wcapabilities;                    // word 49: 一般能力

    ushort  wreserved1;                 // word 50: 保留

    ushort  wpiotiming;                 // word 51: pio時序

    ushort  wdmatiming;                 // word 52: dma時序

        ushort  chsnumber:1;            // 1=word 54-58有效

        ushort  cyclenumber:1;          // 1=word 64-70有效

        ushort  unltradma:1;            // 1=word 88有效

        ushort  reserved:13;

    } wfieldvalidity;                   // word 53: 後續字段有效性标志

    ushort  wnumcurcyls;                // word 54: chs可尋址的柱面數

    ushort  wnumcurheads;               // word 55: chs可尋址的磁頭數

    ushort  wnumcursectorspertrack;     // word 56: chs可尋址每磁道扇區數

    ushort  wcursectorslow;             // word 57: chs可尋址的扇區數低位字

    ushort  wcursectorshigh;            // word 58: chs可尋址的扇區數高位字

        ushort  curnumber:8;            // 目前一次性可讀寫扇區數

        ushort  multi:1;                // 1=已選擇多扇區讀寫

        ushort  reserved1:7;

    } wmultsectorstuff;                 // word 59: 多扇區讀寫設定

    ulong  dwtotalsectors;              // word 60-61: lba可尋址的扇區數

    ushort  wsingleworddma;             // word 62: 單位元組dma支援能力

        ushort  mode0:1;                // 1=支援模式0 (4.17mb/s)

        ushort  mode1:1;                // 1=支援模式1 (13.3mb/s)

        ushort  mode2:1;                // 1=支援模式2 (16.7mb/s)

        ushort  reserved1:5;

        ushort  mode0sel:1;             // 1=已選擇模式0

        ushort  mode1sel:1;             // 1=已選擇模式1

        ushort  mode2sel:1;             // 1=已選擇模式2

        ushort  reserved2:5;

    } wmultiworddma;                    // word 63: 多位元組dma支援能力

        ushort  advpoimodes:8;          // 支援進階poi模式數

        ushort  reserved:8;

    } wpiocapacity;                     // word 64: 進階pio支援能力

    ushort  wminmultiworddmacycle;      // word 65: 多位元組dma傳輸周期的最小值

    ushort  wrecmultiworddmacycle;      // word 66: 多位元組dma傳輸周期的建議值

    ushort  wminpionoflowcycle;         // word 67: 無流控制時pio傳輸周期的最小值

    ushort  wminpoiflowcycle;           // word 68: 有流控制時pio傳輸周期的最小值

    ushort  wreserved69[11];            // word 69-79: 保留

        ushort  reserved1:1;

        ushort  ata1:1;                 // 1=支援ata-1

        ushort  ata2:1;                 // 1=支援ata-2

        ushort  ata3:1;                 // 1=支援ata-3

        ushort  ata4:1;                 // 1=支援ata/atapi-4

        ushort  ata5:1;                 // 1=支援ata/atapi-5

        ushort  ata6:1;                 // 1=支援ata/atapi-6

        ushort  ata7:1;                 // 1=支援ata/atapi-7

        ushort  ata8:1;                 // 1=支援ata/atapi-8

        ushort  ata9:1;                 // 1=支援ata/atapi-9

        ushort  ata10:1;                // 1=支援ata/atapi-10

        ushort  ata11:1;                // 1=支援ata/atapi-11

        ushort  ata12:1;                // 1=支援ata/atapi-12

        ushort  ata13:1;                // 1=支援ata/atapi-13

        ushort  ata14:1;                // 1=支援ata/atapi-14

        ushort  reserved2:1;

    } wmajorversion;                    // word 80: 主版本

    ushort  wminorversion;              // word 81: 副版本

    ushort  wreserved82[6];             // word 82-87: 保留

        ushort  mode0:1;                // 1=支援模式0 (16.7mb/s)

        ushort  mode1:1;                // 1=支援模式1 (25mb/s)

        ushort  mode2:1;                // 1=支援模式2 (33mb/s)

        ushort  mode3:1;                // 1=支援模式3 (44mb/s)

        ushort  mode4:1;                // 1=支援模式4 (66mb/s)

        ushort  mode5:1;                // 1=支援模式5 (100mb/s)

        ushort  mode6:1;                // 1=支援模式6 (133mb/s)

        ushort  mode7:1;                // 1=支援模式7 (166mb/s) ???

        ushort  mode3sel:1;             // 1=已選擇模式3

        ushort  mode4sel:1;             // 1=已選擇模式4

        ushort  mode5sel:1;             // 1=已選擇模式5

        ushort  mode6sel:1;             // 1=已選擇模式6

        ushort  mode7sel:1;             // 1=已選擇模式7

    } wultradma;                        // word 88:  ultra dma支援能力

    ushort    wreserved89[167];         // word 89-255

} idinfo, *pidinfo;

// scsi驅動所需的輸入輸出共用的結構

typedef struct _srb_io_control

   ulong headerlength;        // 頭長度

   uchar signature[8];        // 特征名稱

   ulong timeout;             // 逾時時間

   ulong controlcode;         // 控制碼

   ulong returncode;          // 傳回碼

   ulong length;              // 緩沖區長度

} srb_io_control, *psrb_io_control;

需要引起注意的是idinfo第57-58 word (chs可尋址的扇區數),因為不滿足32位對齊的要求,不可定義為一個ulong字段。lynn mcguire的程式裡正是由于定義為一個ulong字段,導緻該結構不可用。

以下是核心代碼:

// 打開裝置

// filename: 裝置的“檔案名”(裝置路徑)

handle opendevice(lpctstr filename)

    handle hdevice;

    // 打開裝置

    hdevice = ::createfile(filename,            // 檔案名

        generic_read | generic_write,          // 讀寫方式

        file_share_read | file_share_write,    // 共享方式

        null,                    // 預設的安全描述符

        open_existing,           // 建立方式

        0,                       // 不需設定檔案屬性

        null);                   // 不需參照模闆檔案

    return hdevice;

}

// 向驅動發“identify device”指令,獲得裝置資訊

// hdevice: 裝置句柄

// pidinfo:  裝置資訊結構指針

bool identifydevice(handle hdevice, pidinfo pidinfo)

    psendcmdinparams pscip;      // 輸入資料結構指針

    psendcmdoutparams pscop;     // 輸出資料結構指針

    dword dwoutbytes;            // ioctl輸出資料長度

    bool bresult;                // ioctl傳回值

    // 申請輸入/輸出資料結構空間

    pscip = (psendcmdinparams)::globalalloc(lmem_zeroinit, sizeof(sendcmdinparams) - 1);

    pscop = (psendcmdoutparams)::globalalloc(lmem_zeroinit, sizeof(sendcmdoutparams) + sizeof(idinfo) - 1);

    // 指定ata/atapi指令的寄存器值

//    pscip->irdriveregs.bfeaturesreg = 0;

//    pscip->irdriveregs.bsectorcountreg = 0;

//    pscip->irdriveregs.bsectornumberreg = 0;

//    pscip->irdriveregs.bcyllowreg = 0;

//    pscip->irdriveregs.bcylhighreg = 0;

//    pscip->irdriveregs.bdriveheadreg = 0;

    pscip->irdriveregs.bcommandreg = ide_ata_identify;

    // 指定輸入/輸出資料緩沖區大小

    pscip->cbuffersize = 0;

    pscop->cbuffersize = sizeof(idinfo);

    // identify device

    bresult = ::deviceiocontrol(hdevice,        // 裝置句柄

        dfp_receive_drive_data,                 // 指定ioctl

        pscip, sizeof(sendcmdinparams) - 1,     // 輸入資料緩沖區

        pscop, sizeof(sendcmdoutparams) + sizeof(idinfo) - 1,    // 輸出資料緩沖區

        &dwoutbytes,                // 輸出資料長度

        (lpoverlapped)null);        // 用同步i/o

    // 複制裝置參數結構

    ::memcpy(pidinfo, pscop->bbuffer, sizeof(idinfo));

    // 釋放輸入/輸出資料空間

    ::globalfree(pscop);

    ::globalfree(pscip);

    return bresult;

// 向scsi mini-port驅動發“identify device”指令,獲得裝置資訊

bool identifydeviceasscsi(handle hdevice, int ndrive, pidinfo pidinfo)

    psendcmdinparams pscip;     // 輸入資料結構指針

    psendcmdoutparams pscop;    // 輸出資料結構指針

    psrb_io_control psrbio;     // scsi輸入輸出資料結構指針

    dword dwoutbytes;           // ioctl輸出資料長度

    bool bresult;               // ioctl傳回值

    psrbio = (psrb_io_control)::globalalloc(lmem_zeroinit,

        sizeof(srb_io_control) + sizeof(sendcmdoutparams) + sizeof(idinfo) - 1);

    pscip = (psendcmdinparams)((char *)psrbio + sizeof(srb_io_control));

    pscop = (psendcmdoutparams)((char *)psrbio + sizeof(srb_io_control));

    // 填充輸入/輸出資料

    psrbio->headerlength = sizeof(srb_io_control);

    psrbio->timeout = 10000;

    psrbio->length = sizeof(sendcmdoutparams) + sizeof(idinfo) - 1;

    psrbio->controlcode = ioctl_scsi_miniport_identify;

    ::strncpy ((char *)psrbio->signature, "scsidisk", 8);

    pscip->bdrivenumber = ndrive;

    bresult = ::deviceiocontrol(hdevice,    // 裝置句柄

        ioctl_scsi_miniport,                // 指定ioctl

        psrbio, sizeof(srb_io_control) + sizeof(sendcmdinparams) - 1,    // 輸入資料緩沖區

        psrbio, sizeof(srb_io_control) + sizeof(sendcmdoutparams) + sizeof(idinfo) - 1,    // 輸出資料緩沖區

        &dwoutbytes,            // 輸出資料長度

        (lpoverlapped)null);    // 用同步i/o

    ::globalfree(psrbio);

// 将串中的字元兩兩颠倒

// 原因是ata/atapi中的word,與windows采用的位元組順序相反

// 驅動程式中已經将收到的資料全部反過來,我們來個負負得正

void adjuststring(char* str, int len)

    char ch;

    int i;

    // 兩兩颠倒

    for (i = 0; i < len; i += 2)

    {

        ch = str[i];

        str[i] = str[i + 1];

        str[i + 1] = ch;

    }

    // 若是右對齊的,調整為左對齊 (去掉左邊的空格)

    i = 0;

    while ((i < len) && (str[i] == ' ')) i++;

    ::memmove(str, &str[i], len - i);

    // 去掉右邊的空格

    i = len - 1;

    while ((i >= 0) && (str[i] == ' '))

        str[i] = '\0';

        i--;

// 讀取ide硬碟的裝置資訊,必須有足夠權限

// ndrive: 驅動器号(0=第一個硬碟,1=0=第二個硬碟,......)

// pidinfo: 裝置資訊結構指針

bool getphysicaldriveinfoinnt(int ndrive, pidinfo pidinfo)

    handle hdevice;         // 裝置句柄

    bool bresult;           // 傳回結果

    char szfilename[20];    // 檔案名

    hdevice = ::opendevice(szfilename);

    if (hdevice == invalid_handle_value)

        return false;

    bresult = ::identifydevice(hdevice, pidinfo);

    if (bresult)

        // 調整字元串

        ::adjuststring(pidinfo->sserialnumber, 20);

        ::adjuststring(pidinfo->smodelnumber, 40);

        ::adjuststring(pidinfo->sfirmwarerev, 8);

    ::closehandle (hdevice);

// 用scsi驅動讀取ide硬碟的裝置資訊,不受權限制約

// ndrive: 驅動器号(0=primary master, 1=promary slave, 2=secondary master, 3=secondary slave)

bool getidedriveasscsiinfoinnt(int ndrive, pidinfo pidinfo)

    bresult = ::identifydeviceasscsi(hdevice, ndrive%2, pidinfo);

    // 檢查是不是空串

    if (pidinfo->smodelnumber[0] == '\0')

        bresult = false;

q 我注意到ata/atapi裡,以及diskid32裡,有一個“identify packet device”指令,與“identify device”有什麼差別?

a identify device專門用于固定硬碟,而identify packet device用于可移動儲存設備如cdrom、cf、mo、zip、tape等。因為驅動程式的原因,實際上用本例的方法,不管是identify device也好,identify packet device也好,擷取可移動儲存設備的詳細資訊,一般是做不到的。而且除了ide硬碟,對scsi、usb等接口的硬碟也不起作用。除非廠商提供的驅動支援這樣的功能。

q ata/atapi有很多指令,如read sectors, write sectors, security, sleep, standby等,利用上述方法,是否可進行相應操作?

a 應該沒問題。但切記,要慎重慎重再慎重!

q 關于權限問題,請解釋一下好嗎?

a 在nt/2000/xp下,administrator可以管理裝置,上述兩種通路驅動的方法都行。但在user身份下,或者登入到域後,使用者無法通路physicaldrive驅動的核心層,但scsi mini-port驅動卻可以。目前是可以,不知道windows以後的版本是否支援。因為這肯定是一個安全隐患。

另外,我們着重讨論nt/2000/xp中deviceiocontrol的應用,如果需要在98/me中得到包括硬碟序列号在内的更加詳細的資訊,請參考diskid32。