程式語言 - Visual C++ 6.0 - 分析USBView



USBView程式是開發、Debug USB裝置必用的好用工具,該工具是Microsoft DDK的範常式式,該程式範例教導使用者如何寫一個搜尋、列舉USB裝置的程式,但是這個程式是使用Win32 API方式撰寫,沒有一定的底子的使用者確實不容易看懂,加上沒有驅動程式相關經驗的使用者來說,更是不容易瞭解它的運作流程,司徒剛好有看過並且修改過此程式,所以趁著還有些許記憶時,整理這一篇文章並分享給有需要的人

USBView位於$DDK_ROOT\src\usb\usbview資料夾


Win32 API程式的進入點位於WinMain副程式(usbview.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
int WINAPI WinMain (
  HINSTANCE hInstance,
  HINSTANCE hPrevInstance,
  LPSTR     lpszCmdLine,
  int       nCmdShow)
{
  MSG     msg;
  HACCEL  hAccel;
  ghInstance = hInstance;
  ghSplitCursor = LoadCursor(ghInstance,
  MAKEINTRESOURCE(IDC_SPLIT));
 
  if(!ghSplitCursor){
    OOPS();
    return 0;
  }
  hAccel = LoadAccelerators(ghInstance,
  MAKEINTRESOURCE(IDACCEL));
 
  if(!hAccel){
    OOPS();
    return 0;
  }
 
  if(!CreateTextBuffer()){
    return 0;
  }
 
  if(!CreateMainWindow(nCmdShow)){
    return 0;
  }
 
  while(GetMessage(&msg, NULL, 0, 0)){
    if(!TranslateAccelerator(ghMainWnd,
      hAccel,
      &msg) &&
      !IsDialogMessage(ghMainWnd,
      &msg))
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }
  DestroyTextBuffer();
  CHECKFORLEAKS();
  return 1;
}

WinMain副程式需要撰寫的程式內容就是一般Win32 API的處理流程,首先載入Cursor、Accelerator資源檔,接著呼叫CreateTextBuffer副程式配置記憶體,再來就是呼叫CreateMainWindow副程式產生主視窗,目前USBView是使用Dialog作為視窗基礎,所以視窗產生完畢後就是開始做訊息取得、分發的動作,也就是接下來的while迴圈內容,這些處理流程都是很制式的,因為視窗程式都是使用訊息作為傳遞的橋樑,而系統若要傳遞訊息給視窗程式,系統就會將訊息放在視窗的訊息緩衝區,所以主程式必須透過GetMessage函式取得訊息並分發給自己的主程式,那自己的主程式要如何收到訊息呢?在CreateMainWindow副程式會呼叫CreateDialog函式並且註冊一個Callback副程式,而該Callback副程式就是負責接收訊息的副程式

司徒把CreateMainWindow(usbview.c)列出來給大家看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
BOOL CreateMainWindow(int nCmdShow)
{
  RECT rc;
  InitCommonControls();
 
  ghMainWnd = CreateDialog(ghInstance,
    MAKEINTRESOURCE(IDD_MAINDIALOG),
    NULL,
    MainDlgProc);
 
  if(ghMainWnd == NULL){
    OOPS();
    return FALSE;
  }
  GetWindowRect(ghMainWnd, &rc);
  gBarLocation = (rc.right - rc.left) / 3;
  ResizeWindows(FALSE, 0);
  ShowWindow(ghMainWnd, nCmdShow);
  UpdateWindow(ghMainWnd);
  return TRUE;
}

接著如何繼續追蹤程式呢?總不可能每個副程式慢慢看,更不可能使用WinDbg追蹤,因為這樣也太累了,仔細想想,由於USBView是視窗程式,所以,全部的訊息一定會透過Callback副程式做後續處理,所以直接看Callback副程式絕對錯不了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
LRESULT CALLBACK MainDlgProc (
  HWND   hWnd,
  UINT   uMsg,
  WPARAM wParam,
  LPARAM lParam)
{
  switch(uMsg){
    HANDLE_MSG(hWnd, WM_INITDIALOG,     USBView_OnInitDialog);
    HANDLE_MSG(hWnd, WM_CLOSE,          USBView_OnClose);
    HANDLE_MSG(hWnd, WM_COMMAND,        USBView_OnCommand);
    HANDLE_MSG(hWnd, WM_LBUTTONDOWN,    USBView_OnLButtonDown);
    HANDLE_MSG(hWnd, WM_LBUTTONUP,      USBView_OnLButtonUp);
    HANDLE_MSG(hWnd, WM_MOUSEMOVE,      USBView_OnMouseMove);
    HANDLE_MSG(hWnd, WM_SIZE,           USBView_OnSize);
    HANDLE_MSG(hWnd, WM_NOTIFY,         USBView_OnNotify);
    HANDLE_MSG(hWnd, WM_DEVICECHANGE,   USBView_OnDeviceChange);
  }
  return 0;
}

HANDLE_MSG是一個巨集,該巨集如下

1
2
#define HANDLE_MSG(hwnd, message, fn) / 
  case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))

該巨集的意思就是將HANDLE_MSG轉換成HANDLE_ + Message,如:WM_CREATE將會轉換成HANDLE_WM_CREATE,而HANDLE_WM_CREATE也是一個巨集,如下定義

1
2
#define HANDLE_WM_CREATE(hwnd, wParam, lParam, fn) \
  ((fn)((hwnd), (LPCREATESTRUCT)(lParam)) ? 0L : (LRESULT)-1L)

使用者看到這裡應該就可以知道,它其實只是做switch指令的判斷,並且執行fn副程式,然後return執行後的結果,如果使用者不想用這麼難懂的巨集,只要自己寫一個switch指令判斷就可以

司徒將目前USBView的訊息整理如下

訊息功能
WM_INITDIALOG視窗初始化訊息
WM_CLOSE視窗結束訊息
WM_COMMAND接收按鈕的訊息
WM_LBUTTONDOWN滑鼠左鍵按下的訊息
WM_LBUTTONUP滑鼠左鍵放開的訊息
WM_MOUSEMOVE滑鼠移動的訊息
WM_SIZE視窗縮放的訊息
WM_NOTIFY系統通知的訊息
WM_DEVICECHANGE註冊的裝置訊息

整理完訊息之後,我們只需要看WM_INITDIALOG、WM_NOTIFY、WM_DEVICECHANGE這三個訊息就可以,因為其它訊息都是制式的處理手法,不會是我們分析的重點,接著我們先看WM_INITDIALOG(usbview.c)在做什麼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
BOOL USBView_OnInitDialog (
  HWND    hWnd,
  HWND    hWndFocus,
  LPARAM  lParam)
{
  HFONT                           hFont;
  HIMAGELIST                      himl;
  HICON                           hicon;
  DEV_BROADCAST_DEVICEINTERFACE   broadcastInterface;
 
 
  // Register to receive notification when a USB device is plugged in.
  broadcastInterface.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
  broadcastInterface.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
 
  memcpy( &(broadcastInterface.dbcc_classguid),
    &(GUID_CLASS_USB_DEVICE),
    sizeof(struct _GUID));
 
  gNotifyDevHandle = RegisterDeviceNotification(hWnd,
    &broadcastInterface,
    DEVICE_NOTIFY_WINDOW_HANDLE);
 
  // Now register for Hub notifications.
  memcpy( &(broadcastInterface.dbcc_classguid),
    &(GUID_CLASS_USBHUB),
    sizeof(struct _GUID));
 
  gNotifyHubHandle = RegisterDeviceNotification(hWnd,
    &broadcastInterface,
    DEVICE_NOTIFY_WINDOW_HANDLE);
 
  //end add
  ghTreeWnd = GetDlgItem(hWnd, IDC_TREE);
 
  //added
  if((himl = ImageList_Create(15, 15,
    FALSE, 2, 0)) == NULL)
  {
    OOPS();
  }
  hicon = LoadIcon(ghInstance, MAKEINTRESOURCE(IDI_ICON));
  giGoodDevice = ImageList_AddIcon(himl, hicon);
  hicon = LoadIcon(ghInstance, MAKEINTRESOURCE(IDI_BADICON));
  giBadDevice = ImageList_AddIcon(himl, hicon);
  hicon = LoadIcon(ghInstance, MAKEINTRESOURCE(IDI_COMPUTER));
  giComputer = ImageList_AddIcon(himl, hicon);
  hicon = LoadIcon(ghInstance, MAKEINTRESOURCE(IDI_HUB));
  giHub = ImageList_AddIcon(himl, hicon);
  hicon = LoadIcon(ghInstance, MAKEINTRESOURCE(IDI_NODEVICE));
  giNoDevice = ImageList_AddIcon(himl, hicon);
 
  TreeView_SetImageList(ghTreeWnd, himl, TVSIL_NORMAL);
  // end add
  ghEditWnd = GetDlgItem(hWnd, IDC_EDIT);
  ghStatusWnd = GetDlgItem(hWnd, IDC_STATUS);
  ghMainMenu = GetMenu(hWnd);
 
  if(ghMainMenu == NULL){
    OOPS();
  }
 
  hFont  = CreateFont(13,  8, 0, 0,
    400, 0, 0, 0,
    0,   1, 2, 1,
    49,  TEXT("Courier"));
 
  SendMessage(ghEditWnd,
    WM_SETFONT,
    (WPARAM) hFont,
    0);
 
  RefreshTree();
  return FALSE;
}

該副程式也很簡單,只是註冊USB Hub通知(透過RegisterDeviceNotification函式)並且產生TreeView視窗,而每個TreeView的Node也都有特定的圖示表示(透過LoadIcon和ImageList_AddIcon),需要注意的是,透過RegisterDeviceNotification函式註冊的訊息,系統會使用WM_DEVICECHANGE訊息作回調,也就是當註冊的裝置插入或者拔除時,系統都會透過WM_DEVICECHANGE訊息通知主程式,那比較有問題的是GUID部份,使用者怎麼會知道有哪些GUID可以使用呢?其實這一些都是微軟定義的裝置GUID,它們位於USBIODef.h中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* f18a0e88-c30c-11d0-8815-00a0c906bed8 */
DEFINE_GUID(GUID_DEVINTERFACE_USB_HUB,    0xf18a0e88, 0xc30c, 0x11d0, 0x88, 0x15, 0x00, \
  0xa0, 0xc9, 0x06, 0xbe, 0xd8);
 
/* A5DCBF10-6530-11D2-901F-00C04FB951ED */
DEFINE_GUID(GUID_DEVINTERFACE_USB_DEVICE, 0xA5DCBF10L, 0x6530, 0x11D2, 0x90, 0x1F, 0x00, \
  0xC0, 0x4F, 0xB9, 0x51, 0xED);
 
/* 3ABF6F2D-71C4-462a-8A92-1E6861E6AF27 */
DEFINE_GUID(GUID_DEVINTERFACE_USB_HOST_CONTROLLER, 0x3abf6f2d, 0x71c4, 0x462a, 0x8a, 0x92, 0x1e, \
  0x68, 0x61, 0xe6, 0xaf, 0x27);
 
/* 4E623B20-CB14-11D1-B331-00A0C959BBD2 */
DEFINE_GUID(GUID_USB_WMI_STD_DATA, 0x4E623B20L, 0xCB14, 0x11D1, 0xB3, 0x31, 0x00,\
  0xA0, 0xC9, 0x59, 0xBB, 0xD2);
 
/* 4E623B20-CB14-11D1-B331-00A0C959BBD2 */
DEFINE_GUID(GUID_USB_WMI_STD_NOTIFICATION, 0x4E623B20L, 0xCB14, 0x11D1, 0xB3, 0x31, 0x00,\
  0xA0, 0xC9, 0x59, 0xBB, 0xD2);
  
#define GUID_CLASS_USBHUB               GUID_DEVINTERFACE_USB_HUB
#define GUID_CLASS_USB_DEVICE           GUID_DEVINTERFACE_USB_DEVICE
#define GUID_CLASS_USB_HOST_CONTROLLER  GUID_DEVINTERFACE_USB_HOST_CONTROLLER

如果是自己開發的USB裝置以及驅動程式,使用者也可以使用這種註冊方式

我們已經知道WM_DEVICECHANGE是一個重要的副程式,所以它的程式碼可能會很大也很複雜,所以我們把它保留到最後再分析,我們先分析另一個WM_NOTIFY(usbview.c)副程式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
LRESULT USBView_OnNotify (
  HWND    hWnd,
  int     DlgItem,
  LPNMHDR lpNMHdr)
{
  if(lpNMHdr->code == TVN_SELCHANGED){
    HTREEITEM hTreeItem;
    hTreeItem = ((NM_TREEVIEW *)lpNMHdr)->itemNew.hItem;
 
    if(hTreeItem){
      UpdateEditControl(ghEditWnd,
      ghTreeWnd,
      hTreeItem);
    }
  }
  return 0;
}

使用者看到應該會嚇一跳,程式碼怎麼這麼簡單,因為該副程式只做TreeView的顯示處理,也就是當使用者點選某個裝置時,在右邊的EDIT視窗顯示相關訊息,所以顯示裝置訊息的程式就寫在UpdateEditControl副程式裡面,這一部份都只是附加文字到EDIT視窗而已,使用者自己看一下就可以知道了

接著我們看一下USBView最重要的副程式WM_DEVICECHANGE(usbview.c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BOOL USBView_OnDeviceChange (
  HWND  hwnd,
  UINT  uEvent,
  DWORD dwEventData)
{
  if(gDoAutoRefresh){
    switch(uEvent){
    case DBT_DEVICEARRIVAL:
    case DBT_DEVICEREMOVECOMPLETE:
      RefreshTree();
      break;
    }
  }
  return TRUE;
}

該副程式判斷DBT_DEVICEARRIVAL、DBT_DEVICEREMOVECOMPLETE訊息並做列舉的動作,所以繼續往RefreshTree看下去

RefreshTree副程式(usbview.c)如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
VOID RefreshTree (VOID)
{
  CHAR  statusText[128];
  ULONG devicesConnected;
 
  // Clear the selection of the TreeView, so that when the tree is
  // destroyed, the control won't try to constantly "shift" the
  // selection to another item.
  //
  TreeView_SelectItem(ghTreeWnd, NULL);
 
  // Clear the edit control
  //
  SetWindowText(ghEditWnd, "");
 
  // Destroy the current contents of the TreeView
  //
  if(ghTreeRoot){
    WalkTree(ghTreeRoot, CleanupItem, 0);
    TreeView_DeleteAllItems(ghTreeWnd);
    ghTreeRoot = NULL;
  }
 
  // Create the root tree node
  //
  ghTreeRoot = AddLeaf(TVI_ROOT, 0, "My Computer", ComputerIcon);
 
  if(ghTreeRoot != NULL){
    // Enumerate all USB buses and populate the tree
    //
    EnumerateHostControllers(ghTreeRoot, &devicesConnected);
 
    //
    // Expand all tree nodes
    //
    WalkTree(ghTreeRoot, ExpandItem, 0);
 
    // Update Status Line with number of devices connected
    //
    wsprintf(statusText, "Devices Connected: %d   Hubs Connected: %d",
      devicesConnected, TotalHubs);
    SetWindowText(ghStatusWnd, statusText);
  }
  else{
    OOPS();
  }
}

該副程式把TreeView刪除重建並且呼叫EnumerateHostControllers副程式做USB Hub列舉的動作,所以繼續往EnumerateHostControllers副程式看下去

EnumerateHostControllers副程式(enum.c)如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
VOID EnumerateHostControllers (
  HTREEITEM  hTreeParent,
  ULONG     *DevicesConnected)
{
  char        HCName[16];
  int         HCNum;
  HANDLE      hHCDev;
  PCHAR       leafName;
 
  HDEVINFO                         deviceInfo;
  SP_DEVICE_INTERFACE_DATA         deviceInfoData;
  PSP_DEVICE_INTERFACE_DETAIL_DATA deviceDetailData;
  ULONG                            index;
  ULONG                            requiredLength;
 
  TotalDevicesConnected = 0;
  TotalHubs = 0;
 
  // Iterate over some Host Controller names and try to open them.
  //
  for(HCNum = 0; HCNum < NUM_HCS_TO_CHECK; HCNum++){
    wsprintf(HCName, "\\\\.\\HCD%d", HCNum);
    hHCDev = CreateFile(HCName,
      GENERIC_WRITE,
      FILE_SHARE_WRITE,
      NULL,
      OPEN_EXISTING,
      0,
      NULL);
 
    // If the handle is valid, then we've successfully opened a Host
    // Controller.  Display some info about the Host Controller itself,
    // then enumerate the Root Hub attached to the Host Controller.
    //
    if(hHCDev != INVALID_HANDLE_VALUE){
      leafName = HCName + sizeof("\\\\.\\") - sizeof("");
      EnumerateHostController(hTreeParent,
      hHCDev,
      leafName);
 
      CloseHandle(hHCDev);
    }
  }
 
  // Now iterate over host controllers using the new GUID based interface
  //
  deviceInfo = SetupDiGetClassDevs((LPGUID)&GUID_CLASS_USB_HOST_CONTROLLER,
    NULL,
    NULL,
    (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));
 
  if(deviceInfo != INVALID_HANDLE_VALUE){
    deviceInfoData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
 
    for(index=0;
      SetupDiEnumDeviceInterfaces(deviceInfo,
        0,
        (LPGUID)&GUID_CLASS_USB_HOST_CONTROLLER,
        index,
        &deviceInfoData);
        index++)
    {
      SetupDiGetDeviceInterfaceDetail(deviceInfo,
        &deviceInfoData,
        NULL,
        0,
        &requiredLength,
        NULL);
 
      deviceDetailData = GlobalAlloc(GPTR, requiredLength);
      deviceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
 
      SetupDiGetDeviceInterfaceDetail(deviceInfo,
        &deviceInfoData,
        deviceDetailData,
        requiredLength,
        &requiredLength,
        NULL);
 
      hHCDev = CreateFile(deviceDetailData->DevicePath,
        GENERIC_WRITE,
        FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        0,
        NULL);
 
      // If the handle is valid, then we've successfully opened a Host
      // Controller.  Display some info about the Host Controller itself,
      // then enumerate the Root Hub attached to the Host Controller.
      //
      if(hHCDev != INVALID_HANDLE_VALUE){
        leafName = deviceDetailData->DevicePath;
        EnumerateHostController(hTreeParent,
          hHCDev,
          leafName);
 
        CloseHandle(hHCDev);
      }
      GlobalFree(deviceDetailData);
    }
 
    SetupDiDestroyDeviceInfoList(deviceInfo);
  }
  *DevicesConnected = TotalDevicesConnected;
}

該副程式要分成兩部份來看,第一部份是使用CreateFile函式去開啟HCD(Host Control Device)並且列舉USB裝置,而第二部份則是使用SetupAPI函式開啟HCD並且列舉USB裝置

為什麼會有這樣的區分呢?因為驅動程式載入時,驅動程式會使用SymbolLink或GUID註冊名稱給User Mode的應用程式開啟,如果是使用SymbolLink則必須使用第一種方式開啟,反之,若驅動程式使用GUID註冊,則User Mode的應用程式就必須使用SetupAPI函式去取得實際路徑

如果是使用SymbolLink則直接使用CreateFile函式開啟就可以了,如果是使用SetupAPI函式的方式開啟,則需要先取得GUID並且使用此GUID去搜尋是否有該裝置,如果有發現裝置則取得該裝置名稱,然後一樣使用CreateFile函式開啟即可

開啟HCD後,就呼叫EnumerateHostController(目前分析的副程式是EnumerateHostControllers,最後多一個s)做列舉裝置的動作,雖然分成兩種方式開啟HCD,但是緊接著都是一樣呼叫EnumerateHostController副程式處理,所以繼續往EnumerateHostController副程式分析

EnumerateHostController副程式(enum.c)如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
VOID EnumerateHostController (
  HTREEITEM  hTreeParent,
  HANDLE     hHCDev,
  PCHAR      leafName)
{
  PCHAR       driverKeyName;
  PCHAR       deviceDesc;
  PCHAR       deviceId;
  HTREEITEM   hHCItem;
  PCHAR       rootHubName;
  PLIST_ENTRY listEntry;
  PUSBHOSTCONTROLLERINFO hcInfo;
  PUSBHOSTCONTROLLERINFO hcInfoInList;
 
  // Allocate a structure to hold information about this host controller.
  //
  hcInfo = (PUSBHOSTCONTROLLERINFO)ALLOC(sizeof(USBHOSTCONTROLLERINFO));
  if(hcInfo != NULL){
    hcInfo->DeviceInfoType = HostControllerInfo;
    // Obtain the driver key name for this host controller.
    //
    driverKeyName = GetHCDDriverKeyName(hHCDev);
    if(driverKeyName){
      // Don't enumerate this host controller again if it already
      // on the list of enumerated host controllers.
      //
      listEntry = EnumeratedHCListHead.Flink;
      while(listEntry != &EnumeratedHCListHead){
        hcInfoInList = CONTAINING_RECORD(listEntry,
        USBHOSTCONTROLLERINFO,
        ListEntry);
 
        if(strcmp(driverKeyName, hcInfoInList->DriverKey) == 0){
          // Already on the list, exit
          //
          FREE(driverKeyName);
          FREE(hcInfo);
          return;
        }
 
        listEntry = listEntry->Flink;
      }
      // Obtain the device id string for this host controller.
      // (Note: this a tmp global string buffer, make a copy of
      // this string if it will used later.)
      //
      deviceId = DriverNameToDeviceDesc(driverKeyName, TRUE);
      if(deviceId){
        ULONG   ven, dev, subsys, rev;
        if (sscanf(deviceId,
          "PCI\\VEN_%x&DEV_%x&SUBSYS_%x&REV_%x",
          &ven, &dev, &subsys, &rev) != 4)
        {
          OOPS();
        }
        hcInfo->DriverKey = driverKeyName;
        hcInfo->VendorID = ven;
        hcInfo->DeviceID = dev;
        hcInfo->SubSysID = subsys;
        hcInfo->Revision = rev;
      }
      else{
        OOPS();
      }
 
      // Obtain the device description string for this host controller.
      // (Note, this a tmp global string buffer, make a copy of
      // this string if it will be used later.)
      //
      deviceDesc = DriverNameToDeviceDesc(driverKeyName, FALSE);
      if(deviceDesc){
        leafName = deviceDesc;
      }
      else{
        OOPS();
      }
 
      // Add this host controller to the USB device tree view.
      //
      hHCItem = AddLeaf(hTreeParent,
        (LPARAM)hcInfo,
        leafName,
        GoodDeviceIcon);
 
      if(hHCItem){
        // Add this host controller to the list of enumerated
        // host controllers.
        //
        InsertTailList(&EnumeratedHCListHead,
          &hcInfo->ListEntry);
 
        // Get the name of the root hub for this host
        // controller and then enumerate the root hub.
        //
        rootHubName = GetRootHubName(hHCDev);
        if(rootHubName != NULL){
          EnumerateHub(hHCItem,
            rootHubName,
            NULL,      // ConnectionInfo
            NULL,      // ConfigDesc
            NULL,      // StringDescs
            "RootHub"  // DeviceDesc
          );
        }
        else{
          // Failure obtaining root hub name.
          OOPS();
        }
      }
      else{
        // Failure adding host controller to USB device tree
        // view.
        OOPS();
        FREE(driverKeyName);
        FREE(hcInfo);
      }
    }
    else{
      // Failure obtaining driver key name.
      OOPS();
      FREE(hcInfo);
    }
  }
}

該副程式首先取得HCD的DriverKey名稱(透過GetHCDDriverKeyName副程式),使用的是IOCTL_GET_HCD_DRIVERKEY_NAME,該IOCTL是微軟定義的,所有HCD都會支援此IOCTL(因為它是預設支援的IOCTL),而取得的名稱就是HCD註冊在Registry的名稱,那為何要去取得Registry名稱呢?因為有此名稱才能存取Registry裡面的相關資訊,而一般Registry都會紀錄驅動程式的詳細資訊,如果使用者已經知道絕對路徑,那麼使用RegOpenKey函式開啟也可以達到相同的目的

接著呼叫DriverNameToDeviceDesc副程式(透過Registry方式)去取得晶片的名稱,該名稱就是顯示在TreeView上面的名稱,而GetHCDDriverKeyName副程式比較簡單,它使用IOCTL的方式去取得HCD的DriverKey名稱,至於DriverNameToDeviceDesc副程式(devnode.c)的部份,因為它比較特別,所以司徒需要好好解析它一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
PCHAR DriverNameToDeviceDesc (PCHAR DriverName, BOOLEAN DeviceId)
{
  DEVINST     devInst;
  DEVINST     devInstNext;
  CONFIGRET   cr;
  ULONG       walkDone = 0;
  ULONG       len;
 
  // Get Root DevNode
  //
  cr = CM_Locate_DevNode(&devInst,
    NULL,
    0);
 
  if(cr != CR_SUCCESS){
    return NULL;
  }
 
  // Do a depth first search for the DevNode with a matching
  // DriverName value
  //
  while(!walkDone){
    // Get the DriverName value
    //
    len = sizeof(buf);
    cr = CM_Get_DevNode_Registry_Property(devInst,
      CM_DRP_DRIVER,
      NULL,
      buf,
      &len,
      0);
 
    // If the DriverName value matches, return the DeviceDescription
    //
    if(cr == CR_SUCCESS && _stricmp(DriverName, buf) == 0){
      len = sizeof(buf);
      if(DeviceId){
        cr = CM_Get_Device_ID(devInst,
        buf,
        len,
        0);
      }
      else{
        cr = CM_Get_DevNode_Registry_Property(devInst,
          CM_DRP_DEVICEDESC,
          NULL,
          buf,
          &len,
          0);
      }
 
      if(cr == CR_SUCCESS){
        return buf;
      }
      else{
        return NULL;
      }
    }
 
    // This DevNode didn't match, go down a level to the first child.
    //
    cr = CM_Get_Child(&devInstNext,
      devInst,
      0);
 
    if(cr == CR_SUCCESS){
      devInst = devInstNext;
      continue;
    }
 
    // Can't go down any further, go across to the next sibling.  If
    // there are no more siblings, go back up until there is a sibling.
    // If we can't go up any further, we're back at the root and we're
    // done.
    //
    for(;;){
      cr = CM_Get_Sibling(&devInstNext,
        devInst,
        0);
 
      if(cr == CR_SUCCESS){
        devInst = devInstNext;
        break;
      }
 
      cr = CM_Get_Parent(&devInstNext,
        devInst,
        0);
 
      if(cr == CR_SUCCESS){
        devInst = devInstNext;
      }
      else{
        walkDone = 1;
        break;
      }
    }
  }
  return NULL;
}

CM_xxx函數是關於Registry的相關操作,而某些CM_xxx函數是可以使用SetupAPI函式替代的

該副程式首先取得Node Handle(CM_Locate_DevNode),接著取得Registry Property(CM_Get_DevNode_Registry_Property),而Property可以有如下選項

旗標 說明
CM_DEVCAP_LOCKSUPPORTED LockSupported
CM_DEVCAP_EJECTSUPPORTED EjectSupported
CM_DEVCAP_REMOVABLE Removable
CM_DEVCAP_DOCKDEVICE DockDevice
CM_DEVCAP_UNIQUEID UniqueID
CM_DEVCAP_SILENTINSTALL SilentInstall
CM_DEVCAP_RAWDEVICEOK RawDeviceOK
CM_DEVCAP_SURPRISEREMOVALOK SurpriseRemovalOK
CM_DEVCAP_HARDWAREDISABLED HardwareDisabled
CM_DEVCAP_NONDYNAMIC NonDynamic

這一些旗標是可以取得相關資訊的旗標,司徒要比較特別提到的是CM_Get_Parent這個函數,因為當我們取得的資料是HID裝置資料時,其實可以再使用一次CM_Get_Parent取得USB裝置資料,然後再使用一次CM_Get_Parent時,就可以取得Hub的裝置資料,CM_Get_Parent這個函數非常好用,因為一般我們寫User Mode的HID裝置列舉時,都會使用SetupDiEnumDeviceInfo函式去取得裝置資訊,但是,卻不知道如何取得USB裝置資料,所以這個時候只要呼叫CM_Get_Parent函式就可以取得USB裝置的資料,由於HID協定是USB最底層協定,它的上一層協定就是USB協定,所以才會有這樣的順序關係,範常式式如下

1
2
3
4
5
6
7
8
DWORD devInstParent;
BOOLEAN bRet
 
da.cbSize = sizeof(da);
bRet = SetupDiEnumDeviceInfo(hDev, iEnumIndex, &da);
CM_Get_Device_ID(da.DevInst, pDeviceID, sizeof(TCHAR) * 255, 0);
CM_Get_Parent(&devInstParent, da.DevInst, 0);
CM_Get_Device_ID(devInstParent, pDeviceID, sizeof(pDeviceID) * 255, 0);

此段程式可以使用在User Mode的HID列舉階段,透過該段程式就可以取得USB裝置的絕對路徑,因為HID裝置的操作和USB裝置的操作是不一樣的,所以一旦擁有USB裝置的Handle就可以非常方便使用其它相關函式

接著繼續往下分析,DriverNameToDeviceDesc副程式是取得裝置的描述名稱,如:人性化介面裝置,內部使用的方式也是透過CM_xxx函數,接著就是將取得的名稱顯示在TreeView上面,最重要的EnumerateHub副程式,因為它會繼續列舉每個Port的裝置

EnumerateHub副程式(enum.c)如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
VOID EnumerateHub (
  HTREEITEM                           hTreeParent,
  PCHAR                               HubName,
  PUSB_NODE_CONNECTION_INFORMATION_EX ConnectionInfo,
  PUSB_DESCRIPTOR_REQUEST             ConfigDesc,
  PSTRING_DESCRIPTOR_NODE             StringDescs,
  PCHAR                               DeviceDesc)
{
  PUSB_NODE_INFORMATION   hubInfo;
  HANDLE                  hHubDevice;
  HTREEITEM               hItem;
  PCHAR                   deviceName;
  BOOL                    success;
  ULONG                   nBytes;
  PVOID                   info;
  CHAR                    leafName[512]; // XXXXX how big does this have to be?
 
  // Initialize locals to not allocated state so the error cleanup routine
  // only tries to cleanup things that were successfully allocated.
  //
  info        = NULL;
  hubInfo     = NULL;
  hHubDevice  = INVALID_HANDLE_VALUE;
 
  // Allocate some space for a USBDEVICEINFO structure to hold the
  // hub info, hub name, and connection info pointers.  GPTR zero
  // initializes the structure for us.
  //
  if(ConnectionInfo != NULL){
    info = ALLOC(sizeof(USBEXTERNALHUBINFO));
  }
  else{
    info = ALLOC(sizeof(USBROOTHUBINFO));
  }
 
  if(info == NULL){
    OOPS();
    goto EnumerateHubError;
  }
 
  // Allocate some space for a USB_NODE_INFORMATION structure for this Hub,
  //
  hubInfo = (PUSB_NODE_INFORMATION)ALLOC(sizeof(USB_NODE_INFORMATION));
  if(hubInfo == NULL){
    OOPS();
    goto EnumerateHubError;
  }
 
  // Keep copies of the Hub Name, Connection Info, and Configuration
  // Descriptor pointers
  //
  if(ConnectionInfo != NULL){
    ((PUSBEXTERNALHUBINFO)info)->DeviceInfoType = ExternalHubInfo;
    ((PUSBEXTERNALHUBINFO)info)->HubInfo = hubInfo;
    ((PUSBEXTERNALHUBINFO)info)->HubName = HubName;
    ((PUSBEXTERNALHUBINFO)info)->ConnectionInfo = ConnectionInfo;
    ((PUSBEXTERNALHUBINFO)info)->ConfigDesc = ConfigDesc;
    ((PUSBEXTERNALHUBINFO)info)->StringDescs = StringDescs;
  }
  else{
    ((PUSBROOTHUBINFO)info)->DeviceInfoType = RootHubInfo;
    ((PUSBROOTHUBINFO)info)->HubInfo = hubInfo;
    ((PUSBROOTHUBINFO)info)->HubName = HubName;
  }
 
  // Allocate a temp buffer for the full hub device name.
  //
  deviceName = (PCHAR)ALLOC(strlen(HubName) + sizeof("\\\\.\\"));
  if(deviceName == NULL){
    OOPS();
    goto EnumerateHubError;
  }
 
  // Create the full hub device name
  //
  strcpy(deviceName, "\\\\.\\");
  strcpy(deviceName + sizeof("\\\\.\\") - 1, HubName);
 
  // Try to hub the open device
  //
  hHubDevice = CreateFile(deviceName,
    GENERIC_WRITE,
    FILE_SHARE_WRITE,
    NULL,
    OPEN_EXISTING,
    0,
    NULL);
 
  // Done with temp buffer for full hub device name
  //
  FREE(deviceName);
  if(hHubDevice == INVALID_HANDLE_VALUE){
    OOPS();
    goto EnumerateHubError;
  }
 
  //
  // Now query USBHUB for the USB_NODE_INFORMATION structure for this hub.
  // This will tell us the number of downstream ports to enumerate, among
  // other things.
  //
  success = DeviceIoControl(hHubDevice,
    IOCTL_USB_GET_NODE_INFORMATION,
    hubInfo,
    sizeof(USB_NODE_INFORMATION),
    hubInfo,
    sizeof(USB_NODE_INFORMATION),
    &nBytes,
    NULL);
 
  if(!success){
    OOPS();
    goto EnumerateHubError;
  }
 
  // Build the leaf name from the port number and the device description
  //
  if(ConnectionInfo){
    wsprintf(leafName, "[Port%d] ", ConnectionInfo->ConnectionIndex);
    strcat(leafName, ConnectionStatuses[ConnectionInfo->ConnectionStatus]);
    strcat(leafName, " :  ");
  }
  else{
    leafName[0] = 0;
  }
 
  if(DeviceDesc){
    strcat(leafName, DeviceDesc);
  }
  else{
    strcat(leafName, HubName);
  }
  // Now add an item to the TreeView with the PUSBDEVICEINFO pointer info
  // as the LPARAM reference value containing everything we know about the
  // hub.
  //
  hItem = AddLeaf(hTreeParent,
    (LPARAM)info,
    leafName,
    HubIcon);
 
  if(hItem == NULL){
    OOPS();
    goto EnumerateHubError;
  }
 
  // Now recursively enumrate the ports of this hub.
  //
  EnumerateHubPorts(
    hItem,
    hHubDevice,
    hubInfo->u.HubInformation.HubDescriptor.bNumberOfPorts
  );
 
  CloseHandle(hHubDevice);
  return;
 
EnumerateHubError:
  //
  // Clean up any stuff that got allocated
  //
 
  if(hHubDevice != INVALID_HANDLE_VALUE){
    CloseHandle(hHubDevice);
    hHubDevice = INVALID_HANDLE_VALUE;
  }
 
  if(hubInfo){
    FREE(hubInfo);
  }
 
  if(info){
    FREE(info);
  }
 
  if(HubName){
    FREE(HubName);
  }
 
  if(ConnectionInfo){
    FREE(ConnectionInfo);
  }
 
  if(ConfigDesc){
    FREE(ConfigDesc);
  }
 
  if(StringDescs != NULL){
    PSTRING_DESCRIPTOR_NODE Next;
    do{
      Next = StringDescs->Next;
      FREE(StringDescs);
      StringDescs = Next;
    }while(StringDescs != NULL);
  }
}

該副程式開啟Hub(不要跟之前的HCD搞混)並且取得Port的狀況(透過IOCTL_USB_GET_NODE_INFORMATION),而這個Port資訊就是代表有幾個實際Port數,有了這個資訊後,就可以更進一步的取得每個Port的裝置資訊(透過EnumerateHubPorts副程式),因此繼續往EnumerateHubPorts副程式分析,而EnumerateHubPorts副程式應該就是最後結尾的副程式了

EnumerateHubPorts副程式(enum.c)如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
VOID EnumerateHubPorts (
  HTREEITEM   hTreeParent,
  HANDLE      hHubDevice,
  ULONG       NumPorts)
{
  HTREEITEM   hItem;
  ULONG       index;
  BOOL        success;
 
  PUSB_NODE_CONNECTION_INFORMATION_EX connectionInfoEx;
  PUSB_DESCRIPTOR_REQUEST             configDesc;
  PSTRING_DESCRIPTOR_NODE             stringDescs;
  PUSBDEVICEINFO                      info;
 
  PCHAR driverKeyName;
  PCHAR deviceDesc;
  CHAR  leafName[512]; // XXXXX how big does this have to be?
  int   icon;
 
  // Loop over all ports of the hub.
  //
  // Port indices are 1 based, not 0 based.
  //
  for(index=1; index <= NumPorts; index++){
    ULONG nBytesEx;
 
    // Allocate space to hold the connection info for this port.
    // For now, allocate it big enough to hold info for 30 pipes.
    //
    // Endpoint numbers are 0-15.  Endpoint number 0 is the standard
    // control endpoint which is not explicitly listed in the Configuration
    // Descriptor.  There can be an IN endpoint and an OUT endpoint at
    // endpoint numbers 1-15 so there can be a maximum of 30 endpoints
    // per device configuration.
    //
    // Should probably size this dynamically at some point.
    //
    nBytesEx = sizeof(USB_NODE_CONNECTION_INFORMATION_EX) +
      sizeof(USB_PIPE_INFO) * 30;
 
    connectionInfoEx = (PUSB_NODE_CONNECTION_INFORMATION_EX)ALLOC(nBytesEx);
 
    if(connectionInfoEx == NULL){
      OOPS();
      break;
    }
 
    //
    // Now query USBHUB for the USB_NODE_CONNECTION_INFORMATION_EX structure
    // for this port.  This will tell us if a device is attached to this
    // port, among other things.
    //
    connectionInfoEx->ConnectionIndex = index;
    success = DeviceIoControl(hHubDevice,
      IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX,
      connectionInfoEx,
      nBytesEx,
      connectionInfoEx,
      nBytesEx,
      &nBytesEx,
      NULL);
 
    if(!success){
      PUSB_NODE_CONNECTION_INFORMATION    connectionInfo;
      ULONG                               nBytes;
 
      // Try using IOCTL_USB_GET_NODE_CONNECTION_INFORMATION
      // instead of IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX
      //
      nBytes = sizeof(USB_NODE_CONNECTION_INFORMATION) +
        sizeof(USB_PIPE_INFO) * 30;
 
      connectionInfo = (PUSB_NODE_CONNECTION_INFORMATION)ALLOC(nBytes);
      connectionInfo->ConnectionIndex = index;
      success = DeviceIoControl(hHubDevice,
        IOCTL_USB_GET_NODE_CONNECTION_INFORMATION,
        connectionInfo,
        nBytes,
        connectionInfo,
        nBytes,
        &nBytes,
        NULL);
 
      if(!success){
        OOPS();
        FREE(connectionInfo);
        FREE(connectionInfoEx);
        continue;
      }
 
      // Copy IOCTL_USB_GET_NODE_CONNECTION_INFORMATION into
      // IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX structure.
      //
      connectionInfoEx->ConnectionIndex =
      connectionInfo->ConnectionIndex;
      connectionInfoEx->DeviceDescriptor =
      connectionInfo->DeviceDescriptor;
      connectionInfoEx->CurrentConfigurationValue =
      connectionInfo->CurrentConfigurationValue;
      connectionInfoEx->Speed =
      connectionInfo->LowSpeed ? UsbLowSpeed : UsbFullSpeed;
      connectionInfoEx->DeviceIsHub =
      connectionInfo->DeviceIsHub;
      connectionInfoEx->DeviceAddress =
      connectionInfo->DeviceAddress;
      connectionInfoEx->NumberOfOpenPipes =
      connectionInfo->NumberOfOpenPipes;
      connectionInfoEx->ConnectionStatus =
      connectionInfo->ConnectionStatus;
      memcpy(&connectionInfoEx->PipeList[0],
        &connectionInfo->PipeList[0],
      sizeof(USB_PIPE_INFO) * 30);
 
      FREE(connectionInfo);
    }
 
    // Update the count of connected devices
    //
    if(connectionInfoEx->ConnectionStatus == DeviceConnected){
      TotalDevicesConnected++;
    }
 
    if(connectionInfoEx->DeviceIsHub){
      TotalHubs++;
    }
 
    // If there is a device connected, get the Device Description
    //
    deviceDesc = NULL;
    if(connectionInfoEx->ConnectionStatus != NoDeviceConnected){
      driverKeyName = GetDriverKeyName(hHubDevice,
      index);
 
      if(driverKeyName){
        deviceDesc = DriverNameToDeviceDesc(driverKeyName, FALSE);
        FREE(driverKeyName);
      }
    }
 
    // If there is a device connected to the port, try to retrieve the
    // Configuration Descriptor from the device.
    //
    if(gDoConfigDesc &&
      connectionInfoEx->ConnectionStatus == DeviceConnected)
    {
      configDesc = GetConfigDescriptor(hHubDevice,
        index,
        0);
    }
    else{
      configDesc = NULL;
    }
 
    if(configDesc != NULL &&
      AreThereStringDescriptors(&connectionInfoEx->DeviceDescriptor,
      (PUSB_CONFIGURATION_DESCRIPTOR)(configDesc+1)))
    {
      stringDescs = GetAllStringDescriptors(
        hHubDevice,
        index,
        &connectionInfoEx->DeviceDescriptor,
        (PUSB_CONFIGURATION_DESCRIPTOR)(configDesc+1));
    }
    else{
      stringDescs = NULL;
    }
 
    // If the device connected to the port is an external hub, get the
    // name of the external hub and recursively enumerate it.
    //
    if(connectionInfoEx->DeviceIsHub){
      PCHAR extHubName;
      extHubName = GetExternalHubName(hHubDevice,
        index);
 
      if(extHubName != NULL){
        EnumerateHub(hTreeParent, //hPortItem,
          extHubName,
          connectionInfoEx,
          configDesc,
          stringDescs,
          deviceDesc);
      }
    }
    else{
      // Allocate some space for a USBDEVICEINFO structure to hold the
      // Config Descriptors, Strings Descriptors, and connection info
      // pointers.  GPTR zero initializes the structure for us.
      //
      info = (PUSBDEVICEINFO) ALLOC(sizeof(USBDEVICEINFO));
      if(info == NULL){
        OOPS();
        if(configDesc != NULL){
          FREE(configDesc);
        }
        FREE(connectionInfoEx);
        break;
      }
      info->DeviceInfoType = DeviceInfo;
      info->ConnectionInfo = connectionInfoEx;
      info->ConfigDesc = configDesc;
      info->StringDescs = stringDescs;
      wsprintf(leafName, "[Port%d] ", index);
      strcat(leafName, ConnectionStatuses[connectionInfoEx->ConnectionStatus]);
 
      if(deviceDesc){
        strcat(leafName, " :  ");
        strcat(leafName, deviceDesc);
      }
 
      if(connectionInfoEx->ConnectionStatus == NoDeviceConnected){
        icon = NoDeviceIcon;
      }
      else if(connectionInfoEx->CurrentConfigurationValue){
        icon = GoodDeviceIcon;
      }
      else{
        icon = BadDeviceIcon;
      }
 
      hItem = AddLeaf(hTreeParent, //hPortItem,
        (LPARAM)info,
        leafName,
        icon);
    }
  }
}

該副程式取得每個Port的裝置資訊(透過IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX或IOCTL_USB_GET_NODE_CONNECTION_INFORMATION),將每個取得的裝置資訊(GetConfigDescriptor和GetAllStringDescriptors)加入TreeView中,這個副程式比較詭異的地方是再度呼叫EnumerateHub副程式,這樣會導致遞迴呼叫,司徒想了一下才知道,這是因為每個USB Hub可以繼續往下串接USB Hub(USB最多能支援127個裝置、USB Hub最大串接層數是6層),所以使用遞迴反而可以讓程式更簡潔,不失為一個好的寫法,所以這一個部份應該是USBView的精髓所在

司徒認為分析到此應該有很多人還是搞不清楚,所以司徒就畫一個流程圖幫助大家構思它的整體運作