boywhp@gmail.com
www.opscn.com

ndiswrapper简介:
一些usb无线网卡厂商只提供windows下的驱动,那么你可以直接使用ndiswrapper来执行windows上的网卡驱动,是不是很cool呢?其实也算是linux不如windows流行的一个证据。

ndiswrapper项目主页:
http://sourceforge.net/apps/mediawik...itle=Main_Page

入口:wrapper.c
module_init(wrapper_init);
  if (wrapmem_init() || ntoskernel_init() || ndis_init() ||
      wrapndis_init() || usb_init() || wrap_procfs_init() ||
      loader_init()) 
完成各个子模块初始化工作。
重点在loader.c  int loader_init(void)

该函数首先misc_register创建一个杂项设备,然后调用register_devices注册设备
static void register_devices(void),该函数会依次注册PCI以及USB驱动
pci_register_driver(&wrap_pci_driver);
usb_register(&wrap_usb_driver);

以USB为例:
static struct usb_driver wrap_usb_driver = {
  .name = DRIVER_NAME,
  .id_table = wrap_usb_id_table,
  .probe = wrap_pnp_start_usb_device,
  .disconnect = __devexit_p(wrap_pnp_remove_usb_device),
  .suspend = wrap_pnp_suspend_usb_device,
  .resume = wrap_pnp_resume_usb_device,
};

首先看usb设备探测处理,pnp.c wrap_pnp_start_usb_device

load_device.bus = WRAP_USB_BUS;
load_device.vendor = le16_to_cpu(udev->descriptor.idVendor);
load_device.device = le16_to_cpu(udev->descriptor.idProduct);
load_device.subvendor = 0;
load_device.subdevice = 0;
wd = load_wrap_device(&load_device);
成功后调用ret = wrap_pnp_start_device(wd);启动设备

loader.c 
struct wrap_device *load_wrap_device(struct load_device *load_device)
char *argv[] = {"loadndisdriver", WRAP_CMD_LOAD_DEVICE,
#if DEBUG >= 1
    "1",
#else
    "0",
#endif
  UTILS_VERSION, vendor, device,
  subvendor, subdevice, bus, NULL};
  char *env[] = {NULL};
ret = call_usermodehelper("/sbin/loadndisdriver", argv, env, 1);
可见ndiswrapper会通过call_usermodehelper来处理驱动的加载,主要是需要在ring3下做一些参数配置之类的东西,loadndisdriver通过ioctl接口同内核模块通信。

具体实现在loader.c wrapper_ioctl函数
static int wrapper_ioctl(struct inode *inode, struct file *file,
       unsigned int cmd, unsigned long arg)

以WRAP_IOCTL_LOAD_DRIVER为例,我们看ndiswrapper是如何加载windows驱动的。

case WRAP_IOCTL_LOAD_DRIVER:
  load_driver = vmalloc(sizeof(*load_driver));
  if (copy_from_user(load_driver, addr, sizeof(*load_driver)))
    ret = -EFAULT;
  else
    ret = load_user_space_driver(load_driver);

首先从R3程序复制参数,然后调用load_user_space_driver

loader.c load_user_space_driver
static int load_user_space_driver(struct load_driver *load_driver)

  drv_obj = allocate_object(sizeof(*drv_obj), OBJECT_TYPE_DRIVER, NULL);
  drv_obj->drv_ext = kzalloc(sizeof(*(drv_obj->drv_ext)), GFP_KERNEL);

  InitializeListHead(&drv_obj->drv_ext->custom_ext);
  IoAllocateDriverObjectExtension(drv_obj,
              (void *)WRAP_DRIVER_CLIENT_ID,
              sizeof(*wrap_driver),
              (void **)&wrap_driver);

  memset(wrap_driver, 0, sizeof(*wrap_driver));
  InitializeListHead(&wrap_driver->list);
  InitializeListHead(&wrap_driver->settings);

  wrap_driver->drv_obj = drv_obj;
  RtlInitAnsiString(&ansi_reg, "/tmp");
  RtlAnsiStringToUnicodeString(&drv_obj->name, &ansi_reg, TRUE);

  strncpy(wrap_driver->name, load_driver->name, sizeof(wrap_driver->name));
  wrap_driver->name[sizeof(wrap_driver->name)-1] = 0;

  if (load_sys_files(wrap_driver, load_driver) ||
      load_bin_files_info(wrap_driver, load_driver) ||
      load_settings(wrap_driver, load_driver) ||
      start_wrap_driver(wrap_driver) ||
      add_wrap_driver(wrap_driver)) {
该函数完成windows的ndis驱动实际加载以及启动。

loader.c load_sys_files
static int load_sys_files(struct wrap_driver *driver, struct load_driver *load_driver)
该函数首先申请内存,将待加载的sys pe文件复制到申请的内存。
然后link_pe_images(driver->pe_images, driver->num_pe_images)

loader.c link_pe_images
int link_pe_images(struct pe_image *pe_image, unsigned short n)
将依次执行
check_nt_hdr       执行一些检查
fix_pe_image       执行内存节对齐操作
read_exports      如果出函数,添加到全局函数表
fixup_reloc        处理重定位数据修改
fixup_imports      处理导入表
fix_user_shared_data_addr   X64的一些处理
至此windows的ndis二进制镜像正式加载到linux内核。

以fixup_imports为例,看一下ndiswrapper是如何处理NDIS 接口的。
pe_linker.c fixup_imports
static int fixup_imports(void *image, IMAGE_NT_HEADERS *nt_hdr)

  opt_hdr = &nt_hdr->OptionalHeader;
  import_data_dir =
    &opt_hdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
  dirent = RVA2VA(image, import_data_dir->VirtualAddress,
      IMAGE_IMPORT_DESCRIPTOR *);

  for (i = 0; dirent[i].Name; i++) {
    name = RVA2VA(image, dirent[i].Name, char*);

    ret += import(image, &dirent[i], name);
  }

static int import(void *image, IMAGE_IMPORT_DESCRIPTOR *dirent, char *dll)
  lookup_tbl = RVA2VA(image, dirent->u.OriginalFirstThunk, ULONG_PTR *);
  address_tbl = RVA2VA(image, dirent->FirstThunk, ULONG_PTR *);

  for (i = 0; lookup_tbl[i]; i++) {
    symname = RVA2VA(image,
         ((lookup_tbl[i] &
        ~IMAGE_ORDINAL_FLAG) + 2), char *);

    get_export(symname, &adr);
    address_tbl[i] = (ULONG_PTR)adr;
  }
定位到pe的导入函数表,然后依次通过get_export函数查询函数的地址,然后修改地址表。

static int get_export(char *name, generic_func *func)
{
  int i, j;

  struct wrap_export *exports[] = {
    ntoskernel_exports,
    ntoskernel_io_exports,
    ndis_exports,
    crt_exports,
    hal_exports,
    rtl_exports,
#ifdef ENABLE_USB
    usb_exports,
#endif
  };

  for (j = 0; j < ARRAY_SIZE(exports); j++)
    for (i = 0; exports[j][i].name != NULL; i++)
      if (strcmp(exports[j][i].name, name) == 0) {
        *func = exports[j][i].func;
        return 0;
      }

  for (i = 0; i < num_pe_exports; i++)
    if (strcmp(pe_exports[i].name, name) == 0) {
      *func = pe_exports[i].addr;
      return 0;
    }

  return -1;
}
这个函数依次检查所有的导出函数表,以及pe_exports【sys pe镜像导出函数表】返回函数地址。

以ndis_exports表为例;
#include "ndis_exports.h"  
该文件通过mkexport.sh脚本自动生成 ./mkexport.sh ndis.c ndis_exports.h
内容如下:
extern struct wrap_export ndis_export[];
struct wrap_export ndis_export[] = {
  WIN_SYMBOL(NdisAcquireReadWriteLock, 3),
  WIN_SYMBOL(NdisAcquireSpinLock, 1),

其中wrap_export数据结构以及WIN_SYMBOL X86宏定义如下:
struct wrap_export {
  const char *name;
  generic_func func;
};

#define WIN_SYMBOL(name, argc) {#name, (generic_func)name}

WIN_SYMBOL(NdisAcquireReadWriteLock, 3),
即对应{“NdisAcquireReadWriteLock”, NdisAcquireReadWriteLock}

至此整个ndiswrapper的框架大致出来了,最关键的部分NDIS API实现见ndis.c,实现了NDIS导出的133个函数。
在此向作者表示敬意,全世界的linux用户感谢你们的辛勤劳动!

2011-12-27