Analysis on the principle of user-mode direct read/write ports in the NT Environment

Source: Internet
Author: User

The user-mode direct read/write port in the NT environment should have been from to 96
What we talked about when the architecture just came out was a last resort. Because a friend asked me a few days ago about iopm in TSS.
Table problems, while most of the articles available in this regard on the Internet are just a general discussion. There are no principles for empty implementation methods, and there is no way to directly reference them. These articles trace the source from Dale.
Robert ts published direct port I/O and windows on dr. Dobb's journal in May
The article NT is described here. Unfortunately, this article requires the membership permission to read it. I will not be able to do anything but myself.
Application articles based on this principle are everywhere on the Internet. For example, a simple encapsulation of porttalk is enough to meet most of the requirements:

The following will analyze the implementation principles of the functions in combination with the NT source code, and learn how to implement the functions and why.

Unlike the dos and Win9x environments, NT
User-state programs run in a strictly restricted environment. Therefore, some special resources such as port access cannot be directly exposed to users to avoid conflicts or impact on system stability. As we know
Port operation commands such as in/out are classified as privileged commands, and user-State program calls may cause exceptions.
This restriction is implemented through a two-layer mechanism: the iopl (I/O privilege level) Flag bit and
Iopm (I/O permission bit map) in TSS (Task state segment)
Provides a flexible two-level control mechanism. (Intel architecture Sofware developer's Manual V1: 12.5)
We know that in the X86 architecture, the privilege is generally divided into 0-3 four rings, while in the NT environment, only the core State RING 0 and the user State ring 3 are used. Eflags
The iopl flag is used to specify the privileged levels of the current task that can use the I/O command. This flag consists of the eflags register 12/13
Bit storage. The privilege level that can use the I/O command must be smaller than or equal to the current iopl value. This flag is generally set to 0, and can only be indicated by the popf and iret commands
Ring 0 ). This ensures that the kernel can completely limit the user-State program's inability to directly use I/O.
And other privileged commands, but can be opened through the iopm network to be discussed immediately. (I/O sensitive commands restricted by iopl include: In, INS,
Out, outs, CLI, STI)
If the current privilege level CPL (current privilege level) is greater than iopl
Determine whether access to this port is allowed in special cases. Tss is the state storage area related to each task. It stores the basic information required for State (context) switching, such as General registers (eax,
(ESP, etc.), segment Selection Sub-(CS, DS, etc.), eflags, EIP, and other content that may be dynamically changed, including
And other static content. The intel Manual defines that the minimum length of TSS is 104 bytes, and the last word is iopm relative to TSS
. Generally, the operating system has customized TSS to a certain extent. For example, the TSS structure (ntos/INC/i386.h: 879) in the NT architecture is roughly as follows:

Typedef struct _ ktss
{
Ushort backlink;

//...

Ushort flags;

Ushort iomapbase;

Kiio_access_map iomaps [iopm_count];

Kint_direction_map intdiremap map;
} Ktss, * pktss;

Because the TSS of the current task has a separate tr (Task register) Register (Intel architecture Sofware
Developer's Manual v3: 6.2.3) saves the 16-bit segment selection and 32-bit base offset. Therefore, the pseudo code for the system to process the I/O commands can be expressed as follows:

Typedef struct {
Unsigned limit: 16;
Unsigned baselo: 16;
Unsigned basemid: 8;
Unsigned type: 4;
Unsigned system: 1;
Unsigned DPL: 2;
Unsigned present: 1;
Unsigned limithi: 4;
Unsigned available: 1;
Unsigned zero: 1;
Unsigned size: 1;
Unsigned granularity: 1;
Unsigned basehi: 8;
} Gdtent;

Typedef struct {
Unsigned short limit;
Gdtent * base;
} Gdreg;

Bool checkiopermission (word port)
{
If (CPL <= eflags. iopl) return true;

Gdtregs;
Word taskseg;

_ Asm cli; // disable interruption
_ ASM sgdt gdreg; // obtain the gdt address
_ Asm str taskseg; // obtain the TSS selection sub-index

Gdtent * ptaskgdt = gdreg. Base + (taskseg> 3); // get the TSS descriptor address

Ktss * PTSS = (pvoid) (ptaskgdt-> baselo | (ptaskgdt-> basemid
<16) | (ptaskgdt-> basehi <24); // calculate the TSS base address

Char * piopm = (char *) PTSS + PTSS-> iomapbase); // calculate the iopm base address

Size_t Pos = port> 3, idx = Port & 0xf;

If (piopm + POS)> (PTSS + taskgdt-> limit ))
{
Throw generalprotectionexception ();
}

_ Asm sti;

Return (piopm [POS] & (1 <idx) = (1 <idx );
}

First, the system checks whether the current privileged level CPL is smaller than the iopl of eflags. Then, it obtains the TSS selection sub-index from the tr register and calculates
The TSS descriptor address. The iopm address can be obtained through the TSS base address and iopm offset. Finally, the iopm address is queried Based on the port.
Content to determine whether to allow operations on this port.
We can use the windbg + livekd tool to check the actual situation in a system:

// Display PCR
Kd>! PCR
Kpcr for processor 0 at ffdff000:
Major 1 minor 1
Nttib. exceptionlist: f460fbfc
Nttib. stackbase: 00000000
Nttib. stacklimit: 00000000
Nttib. subsystemtib: 80042000
Nttib. Version: 2568915f
Nttib. userpointer: 00000001
Nttib. selftib: 7ffdd000

Selfpcr: ffdff000
Prcb: ffdff120
IRQL: 00000000
IRR: 00000000
IDR: ffffffff
Interruptmode: 00000000
IDT: 8003f400
Gdt: 8003f000
Tss: 80042000

Currentthread: 826a7788
Nextthread: 00000000
Idlethread: 80569280

Dpcqueue:

// Display TSS
Kd> dd 80042000
80042000 eb3d76f6 f460fde0 0d8b0010 00441f30
80042010 0674c085 24f8448b 048b03eb 33026a0b
80042020 e85051c9 fffffd6c 1474c085 50413881
80042030 08744349 04c38347 c972fe3b 0272fe3b
80042040 5e5fc033 8b55c35b 8b5151ec 008b0845
80042050 4453523d f8458954 30a10a75 e900441f

80042060 00000000 20ac0000 18000004 00000018 // iopm offset is 20ac, ktss. iomapbase

80042070 00000000 00000000 00000000 00000000
80042080 00000000 00000000 ffffffff ffffff // TSS built-in iopm, ktss. iomaps [0]
80042090 ffffffff ffffffffffffff ffffffffff
...
80044080 ffffffff ffffffffffffff 18000004
80044090 00000018 00000000 00000000 00000000
80010000a0 00000000 00000000 00000000 cbb70fd9
800347b0 75ff5051 fc4d890c 0009e6e8 06896600

We can see that the content of TSS is stored at 0x80042000. The 0x64 offset content 0x20ac is the offset of the current iopm, while the 0x88
A pile of 0 xffffffff at the offset is the content of ktss. iomaps [0]. The content of this iopm table will be parsed in detail; and 0x20ac
The iopm content is actually used.

Based on this principle, Dale Roberts proposed several methods to allow the user mode to access the port. In the final analysis, it is all about the iopm offset and content of the TSS.

In addition, some articles also use the same principle, such as the article "I/O of any ring 3 process under NT. It is worth noting that the original text here chooses to increase the TSS length limit.
0xf00 actually limits the number of accessible ports to be less than 0xf00*8 = 30720. This hard limitation should be taken into account when using this method. 0xf00
To ensure the extension of the TSS length, does not cause page errors. Because the original TSS length is generally 0x20ab
Does not cause cross-page issues. In totalio. C, the problem is described as follows:

Since we can safely extend the TSS only to the end of the physical
Memory Page in which it lies, the I/O access is granted only up to Port
0xf00. accesses beyond this port address will still generate
Exceptions.

To view gdt table items of TSS in the actual environment, follow these steps (0x28> 3 = 5 ):

Kd> RM 0x100
Last set Context
Kd> r
Last set context:
GDTR = 8003f000 gdtl = 03ff idtr = 8003f400 idtl = 07ff TR = 0028 ldtr = 0000

Kd> dd 8003f000
8003f000 00000000 00000000 0000 FFFF 00cf9b00
8003f010 0000 FFFF 00cf9300 0000 FFFF 00cffb00
8003f020 0000 FFFF 00cff300 200020ab 80008b04 // TSS limit 20ab
8003f030 f0000001 ffc093df d0000fff 7f40f3fd

The complete code for setting the TSS length limit in totalio. C is as follows:

Void settsslimit (INT size)
{
Gdtregs;
Gdtent * g;
Short taskseg;

_ Asm cli; // don't get interrupted!
_ ASM sgdt gdreg; // get gdt address
_ Asm str taskseg; // get TSS selector Index
G = gdreg. Base + (taskseg> 3); // get PTR to TSS Descriptor
G-> Limit = size; // modify TSS segment limit
//
// Must set selector type field to 9, to indicate the task is
// Not busy. Otherwise the LTr instruction causes a fault.
//
G-> type = 9; // mark TSS as "not busy"
//
// We must do a load of the task register, else the processor
// Never sees the new TSS selector limit.
//
_ Asm ltr taskseg; // reload task register (TR)
_ Asm sti; // Let interrupts continue
}

Here, the TSS type is set to 9, indicating that the descriptor type is 32-bit TSS non-busy Descriptor (Intel architecture Sofware developer's Manual v3: 3.5 ).
This method allows access to any restricted port of the system by directly operating system register, but it is not a perfect solution. Relatively speaking, functions are not disclosed through the operating system.
Kew.setioaccessmap, kew.queryioaccessmap and kew.iosetaccessprocess
The method for enabling special ports for independent processes to allow access is more elegant. Next, let's take a closer look at the principles and usage of these functions.
Based on the analysis of the ktss structure and actual memory in the NT system, we can understand that in the NT environment, each process maintains a separate TSS memory area
The TSS maintains an iopm table with all mark positions 1, and at the end of the TSS, it also maintains another iopm that is actually responsible for port management.
Table. Ke386setioaccessmap function (ntos/ke/i386/iopm. C: 80) and kew.queryioaccessmap
Functions (ntos/ke/i386/iopm. C: 235) are the functions provided by the system to read and write these two iopm tables. While
The ke386iosetaccessprocess function (ntos/ke/i386/iopm. C: 318) specifies the iopm table used by the process.

Boolean kew.queryioaccessmap (ulong mapnumber, pkio_access_map ioaccessmap );
Boolean ke386setioaccessmap (ulong mapnumber, pkio_access_map ioaccessmap );

Boolean ke386iosetaccessprocess (pkprocess process, ulong mapnumber );

For the first two functions, mapnumber specifies the table to be operated on. The system defines a io_access_map_none = 0 CONSTANT TO INDICATE
The real iopm table behind the TSS, and other indexes correspond to the ktss. iomaps [] array. This array has only one table item in most cases, that is
When mapnumber is 0, it indicates the iopm behind the TSS; If mapnumber is 1, it indicates the ktss. iomaps [0] inside the TSS.
The kemo-queryioaccessmap function simply judges the ioaccessmap function based on the mapnumber.
Set all content bits (mapnumber = 0), or copy the corresponding table from TSS (0 <mapnumber <= iopm_count
= 1 ). The pseudocode is as follows:

# Define iopm_count 1
# Define iopm_size 8192 // size of map callers can set.

Boolean kew.queryioaccessmap (ulong mapnumber, pkio_access_map ioaccessmap)
{
If (mapnumber> iopm_count) return false;

If (mapnumber = io_access_map_none)
{
Memset (ioaccessmap,-1, iopm_size );
}
Else
{
Void * piopm = & (kipcr ()-> Tss-> iomaps [MapNumber-1]. iomap );

Memcpy (ioaccessmap, piopm, iopm_size );
}
Return true;
}

While ke386setioaccessmap directly returns false when mapnumber is 0, because TSS
The following table cannot be modified. In other cases, the function copies the content in ioaccessmap back to the iopm of TSS.
And notify other processors to reload the iopm table in the case of multiple processors. The pseudocode is as follows:

Boolean ke386setioaccessmap (ulong mapnumber, pkio_access_map ioaccessmap)
{
If (mapnumber> iopm_count) | (mapnumber = io_access_map_none) return false;

Void * piopm = & (kipcr ()-> Tss-> iomaps [MapNumber-1]. iomap );

Memcpy (piopm, ioaccessmap, iopm_size );

Kipcr ()-> Tss-> iomapbase = getcurrentprocess ()-> iopmoffset;

// Notify other processors to reset iopm

Return true;
}

The ke386iosetaccessprocess function simply modifies the iopm offset of the current TSS to the iopm Table offset specified by mapnumber, and notifies other CPUs to re-load the iopm offset when multiple CPUs exist. The offset calculation algorithm is as follows:

# Define kicomputeiopmoffset (mapnumber )/
(Mapnumber = io_access_map_none )? /
(Ushort) (sizeof (ktss )):/
(Ushort) (field_offset (ktss, IOS maps [MapNumber-1]. iomap ))

Ushort mapoffset = kicomputeiopmoffset (mapnumber );

The complete process code is as follows:

# Define iopm_size 8192 // size of map callers can set.

Typedef uchar kio_access_map [iopm_size];
Typedef kio_access_map * pkio_access_map;

Pkio_access_map iopm_local = mmallocatenoncachedmemory (sizeof (iopm ));
If (iopm_local = 0)
Return status_insufficient_resources;

Kew.queryioaccessmap (1, iopm_local );

// Modify the iopm_local content to open the required port

Ke386setioaccessmap (1, iopm_local );
Ke386iosetaccessprocess (psgetcurrentprocess (), 1 );

For more information about the code, see the source code of porttalk and totalio.

References:

1. Intel architecture Sofware developer's Manual

2. Direct port I/O and Windows NT, Dale Robert TS
Http://www.ddj.com/articles/1996/9605/

3. porttalk-a Windows nt I/O port device driver, Craig peaco CK
Http://www.beyondlogic.org/porttalk/porttalk.htm

Porttalk-port driver for Windows NT/2000, Song Yongqiang)
Http://www.daqchina.net/daqchina/acquire/ioaccess.htm? Curtime= 1084629289

4. Any ports I/O and sinister of all ring 3 processes under NT (excerpt)
Http://www.xfocus.net/articles/200303/496.html

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.