Question on KeAquireSpinLock

My oh my it’s educational to debug Windows driver code written by Linux
people.

The driver I’m debugging has some wrapper functions around DDK functions.
The following two are my current focus, as I had a crash that verifier said
was caused by releasing a spinlock and lowering the IRQL to an impossible
value (30 specifically).

CL_INLINE void

cl_spinlock_acquire(

IN cl_spinlock_t* const p_spinlock )

{

CL_ASSERT( p_spinlock );

KeAcquireSpinLock( &p_spinlock->lock, &p_spinlock->irql );

}

CL_INLINE void

cl_spinlock_release(

IN cl_spinlock_t* const p_spinlock )

{

CL_ASSERT( p_spinlock );

KeReleaseSpinLock( &p_spinlock->lock, p_spinlock->irql );

}

The structure cl_spinlock_t contains the Windows spinlock and the old IRQL.

Notice the old IRQL value to KeAquireSpinLock as shared between all callers
of this function. When I write Windows driver code I always make it a local
variable. The DDK docs say “The current IRQL is saved in OldIrql. Then, the
current IRQL is reset to DISPATCH_LEVEL, and the specified spin lock is
acquired.” Which if accurate, means the old IRQL value is stored BEFORE the
lock is acquired?

This would imply:

CPU 1 running at PASSIVE_LEVEL calls cl_spinlock_acquire passing the lock
structure, which saves PASSIVE_LEVEL in the shared old IRQL variable and
gets the spinlock

CPU 2 running at DISPATCH_LEVEL calls cl_spinlock_acquire passing the lock
structure, which saves DISPATCH_LEVEL in the shared old IRQL variable and
spins on the lock

CPU 1 calls cl_spinlock_release passing the lock structure, which
incorrectly restores DISPATCH_LEVEL from the shared old IRQL variable and
releases the spinlock

CPU 2 acquires the lock and continues

CPU 2 calls cl_spinlock_release passing the lock structure, which correctly
restores DISPATCH_LEVEL from the shared old IRQL variable and releases the
spinlock

.bad things happen as CPU 1 is not supposed to be at DISPATCH_LEVEL.

So the question is, does the old IRQL variable passed to KeAquireSpinLock
need to be unique to each caller or not? I looked at the assembler for both
OS functions and it seems like the DDK documentation isn’t correct for my
x86 processor, although perhaps on some processor it is correct. For an x86,
the lock prefix in those OS functions should cause a memory barrier, and
prevent the old IRQL from getting updated if the lock isn’t acquired. The
semantics of the DDK docs is safer, although not helping me determine if
this code is broken. The DDK docs don’t say the old IRQL needs to be a
local, even though the description implies it does to prevent the above
problem.

Opinions?

  • Jan

On Oct 18, 2005, at 2:22 AM, Jan Bottorff wrote:

Notice the old IRQL value to KeAquireSpinLock as shared between all
callers of this function. When I write Windows driver code I always
make it a local variable. The DDK docs say ?The current IRQL is
saved in OldIrql. Then, the current IRQL is reset to
DISPATCH_LEVEL, and the specified spin lock is acquired.? Which if
accurate, means the old IRQL value is stored BEFORE the lock is
acquired?
Personally I would view the function as opaque, and not worry about
what order it does this sort of thing in. This, of course, means that
if you’re sharing OldIrql, you are open to a race condition, as you
describe below. I’d fix that before I’d worry about what happens in
what order. You should have a separate OldIrql value per caller (and
one way to do that is to use a local variable).

-sd


Steve Dispensa
MVP - Windows DDK
www.kernelmustard.com

Ahile I agree with your comment about Windows driver code being written
by Linux people, I don’t see a problem with those wrappers provided they
are used correctly.

Are you sure that the old irql is shared? It’s part of the structure
being passed in to the caller. Does each caller have its own copy of the
structure? If so, then it is not being shared and that is not your
problem.

Beverly


From: xxxxx@lists.osr.com
[mailto:xxxxx@lists.osr.com] On Behalf Of Jan Bottorff
Sent: Tuesday, October 18, 2005 3:22 AM
To: Windows System Software Devs Interest List
Subject: [ntdev] Question on KeAquireSpinLock

My oh my it’s educational to debug Windows driver code written by Linux
people…

The driver I’m debugging has some wrapper functions around DDK
functions. The following two are my current focus, as I had a crash that
verifier said was caused by releasing a spinlock and lowering the IRQL
to an impossible value (30 specifically).

CL_INLINE void

cl_spinlock_acquire(

IN cl_spinlock_t* const p_spinlock )

{

CL_ASSERT( p_spinlock );

KeAcquireSpinLock( &p_spinlock->lock, &p_spinlock->irql );

}

CL_INLINE void

cl_spinlock_release(

IN cl_spinlock_t* const p_spinlock )

{

CL_ASSERT( p_spinlock );

KeReleaseSpinLock( &p_spinlock->lock, p_spinlock->irql );

}

The structure cl_spinlock_t contains the Windows spinlock and the old
IRQL.

Notice the old IRQL value to KeAquireSpinLock as shared between all
callers of this function. When I write Windows driver code I always make
it a local variable. The DDK docs say “The current IRQL is saved in
OldIrql. Then, the current IRQL is reset to DISPATCH_LEVEL, and the
specified spin lock is acquired.” Which if accurate, means the old IRQL
value is stored BEFORE the lock is acquired?

This would imply:

CPU 1 running at PASSIVE_LEVEL calls cl_spinlock_acquire passing the
lock structure, which saves PASSIVE_LEVEL in the shared old IRQL
variable and gets the spinlock

CPU 2 running at DISPATCH_LEVEL calls cl_spinlock_acquire passing the
lock structure, which saves DISPATCH_LEVEL in the shared old IRQL
variable and spins on the lock

CPU 1 calls cl_spinlock_release passing the lock structure, which
incorrectly restores DISPATCH_LEVEL from the shared old IRQL variable
and releases the spinlock

CPU 2 acquires the lock and continues

CPU 2 calls cl_spinlock_release passing the lock structure, which
correctly restores DISPATCH_LEVEL from the shared old IRQL variable and
releases the spinlock

…bad things happen as CPU 1 is not supposed to be at DISPATCH_LEVEL…

So the question is, does the old IRQL variable passed to
KeAquireSpinLock need to be unique to each caller or not? I looked at
the assembler for both OS functions and it seems like the DDK
documentation isn’t correct for my x86 processor, although perhaps on
some processor it is correct. For an x86, the lock prefix in those OS
functions should cause a memory barrier, and prevent the old IRQL from
getting updated if the lock isn’t acquired. The semantics of the DDK
docs is safer, although not helping me determine if this code is broken.
The DDK docs don’t say the old IRQL needs to be a local, even though the
description implies it does to prevent the above problem.

Opinions?

  • Jan

Questions? First check the Kernel Driver FAQ at
http://www.osronline.com/article.cfm?id=256

You are currently subscribed to ntdev as: unknown lmsubst tag argument:
‘’
To unsubscribe send a blank email to xxxxx@lists.osr.com

> This, of course, means that if you’re sharing OldIrql, you are open to a
race condition, as you describe below.

No, sharing the OldIrql value is safe. From WINDDK 2600 <ndis.h>:

typedef struct _NDIS_SPIN_LOCK
{
KSPIN_LOCK SpinLock;
KIRQL OldIrql;
} NDIS_SPIN_LOCK, * PNDIS_SPIN_LOCK;

#define NdisAcquireSpinLock(_SpinLock)
KeAcquireSpinLock(&(_SpinLock)->SpinLock, &(_SpinLock)->OldIrql)

#define NdisReleaseSpinLock(_SpinLock)
KeReleaseSpinLock(&(_SpinLock)->SpinLock,(_SpinLock)->OldIrql)

If sharing OldIrql was unsafe, then NDIS drivers wouldn’t work.

– arlie

________________________________

From: xxxxx@lists.osr.com
[mailto:xxxxx@lists.osr.com] On Behalf Of Steve Dispensa
Sent: Tuesday, October 18, 2005 10:53 AM
To: Windows System Software Devs Interest List
Subject: Re: [ntdev] Question on KeAquireSpinLock

On Oct 18, 2005, at 2:22 AM, Jan Bottorff wrote:

Notice the old IRQL value to KeAquireSpinLock as shared
between all callers of this function. When I write Windows driver code I
always make it a local variable. The DDK docs say “The current IRQL is saved
in OldIrql. Then, the current IRQL is reset to DISPATCH_LEVEL, and the
specified spin lock is acquired.” Which if accurate, means the old IRQL
value is stored BEFORE the lock is acquired?

Personally I would view the function as opaque, and not worry about
what order it does this sort of thing in. This, of course, means that if
you’re sharing OldIrql, you are open to a race condition, as you describe
below. I’d fix that before I’d worry about what happens in what order. You
should have a separate OldIrql value per caller (and one way to do that is
to use a local variable).

-sd

----------------------------------

Steve Dispensa

MVP - Windows DDK

www.kernelmustard.com


Questions? First check the Kernel Driver FAQ at
http://www.osronline.com/article.cfm?id=256

You are currently subscribed to ntdev as: unknown lmsubst tag argument: ‘’
To unsubscribe send a blank email to xxxxx@lists.osr.com</ndis.h>

While this is a very good argument, I have always used a local variable,
even if the OldIrql was stored in the structure, i.e.

KIRQL irql;

KeAcquireSpinLock(&p->Lock, &irql);
p->OldIrql = irql;

I don’t know what KeAcquireSpinLock does in terms of using the PKIRQL as
a temporary space while there is spinning on the lock, even if NDIS
would theoretically be broken if it were to use it as a temporary
storage location. I guess I am paranoid ;).

d

-----Original Message-----
From: xxxxx@lists.osr.com
[mailto:xxxxx@lists.osr.com] On Behalf Of Arlie Davis
Sent: Tuesday, October 18, 2005 8:35 AM
To: Windows System Software Devs Interest List
Subject: RE: [ntdev] Question on KeAquireSpinLock

This, of course, means that if you’re sharing OldIrql, you are open to
a
race condition, as you describe below.

No, sharing the OldIrql value is safe. From WINDDK 2600 <ndis.h>:

typedef struct _NDIS_SPIN_LOCK
{
KSPIN_LOCK SpinLock;
KIRQL OldIrql;
} NDIS_SPIN_LOCK, * PNDIS_SPIN_LOCK;

#define NdisAcquireSpinLock(_SpinLock)
KeAcquireSpinLock(&(_SpinLock)->SpinLock, &(_SpinLock)->OldIrql)

#define NdisReleaseSpinLock(_SpinLock)
KeReleaseSpinLock(&(_SpinLock)->SpinLock,(_SpinLock)->OldIrql)

If sharing OldIrql was unsafe, then NDIS drivers wouldn’t work.

– arlie

________________________________

From: xxxxx@lists.osr.com
[mailto:xxxxx@lists.osr.com] On Behalf Of Steve Dispensa
Sent: Tuesday, October 18, 2005 10:53 AM
To: Windows System Software Devs Interest List
Subject: Re: [ntdev] Question on KeAquireSpinLock

On Oct 18, 2005, at 2:22 AM, Jan Bottorff wrote:

Notice the old IRQL value to KeAquireSpinLock as shared
between all callers of this function. When I write Windows driver code I
always make it a local variable. The DDK docs say “The current IRQL is
saved
in OldIrql. Then, the current IRQL is reset to DISPATCH_LEVEL, and the
specified spin lock is acquired.” Which if accurate, means the old IRQL
value is stored BEFORE the lock is acquired?

Personally I would view the function as opaque, and not worry
about
what order it does this sort of thing in. This, of course, means that if
you’re sharing OldIrql, you are open to a race condition, as you
describe
below. I’d fix that before I’d worry about what happens in what order.
You
should have a separate OldIrql value per caller (and one way to do that
is
to use a local variable).

-sd

----------------------------------

Steve Dispensa

MVP - Windows DDK

www.kernelmustard.com


Questions? First check the Kernel Driver FAQ at
http://www.osronline.com/article.cfm?id=256

You are currently subscribed to ntdev as: unknown lmsubst tag argument:
‘’
To unsubscribe send a blank email to xxxxx@lists.osr.com


Questions? First check the Kernel Driver FAQ at
http://www.osronline.com/article.cfm?id=256

You are currently subscribed to ntdev as: xxxxx@microsoft.com
To unsubscribe send a blank email to xxxxx@lists.osr.com</ndis.h>

Doron,

Not paranoid, just careful, which is why so many of us respect and
appreciate your posts.


Don Burn (MVP, Windows DDK)
Windows 2k/XP/2k3 Filesystem and Driver Consulting
Remove StopSpam from the email to reply

“Doron Holan” wrote in message
news:xxxxx@ntdev…
While this is a very good argument, I have always used a local variable,
even if the OldIrql was stored in the structure, i.e.

KIRQL irql;

KeAcquireSpinLock(&p->Lock, &irql);
p->OldIrql = irql;

I don’t know what KeAcquireSpinLock does in terms of using the PKIRQL as
a temporary space while there is spinning on the lock, even if NDIS
would theoretically be broken if it were to use it as a temporary
storage location. I guess I am paranoid ;).

d

On Oct 18, 2005, at 10:35 AM, Arlie Davis wrote:

> This, of course, means that if you’re sharing OldIrql, you are
> open to a
>
race condition, as you describe below.

If sharing OldIrql was unsafe, then NDIS drivers wouldn’t work.

That is an excellent point.

I’m not really interested in arguing wither it would work - but I do
think it behooves the implementor to follow the docs (usually), and
the docs for KeAcquireSpinLock() indicate that it would race. The
NDIS guys know something I don’t re: this topic, which is totally
unsurprising. :slight_smile:

-sd

I don’t see any problem at all.
inline…

CPU 1 running at PASSIVE_LEVEL calls
cl_spinlock_acquire passing the lock
structure, which saves PASSIVE_LEVEL in the shared
old IRQL variable and
gets the spinlock

Ok.

CPU 2 running at DISPATCH_LEVEL calls
cl_spinlock_acquire passing the lock
structure, which saves DISPATCH_LEVEL in the shared
old IRQL variable and
spins on the lock

Nope, lock has been held by CPU_1, KeAcquireSpl will
have to spin. It can not update the OldIrql until the
lock is owned. Note that in KeAcquireSpinLock, OldIrql
is an **output** argment. However, I do agree with
others that using local var looks nicer.

CPU 1 calls cl_spinlock_release passing the lock
structure, which
incorrectly restores DISPATCH_LEVEL from the shared
old IRQL variable and
releases the spinlock

As said eariler, no one should have changed the
OldIrql unless there’s memory corruption. well, then
you’d have more problems. So still it’s whatever level
before CPU_1 acquired the spinlock, right?

I don’t believe it’s broken. All NDIS drivers should
have been broken since day one otherwise.


Calvin Guan (Windows DDK MVP)
NetXtreme Longhorn Miniport Prime
Broadcom Corp. www.broadcom.com


Do You Yahoo!?
Tired of spam? Yahoo! Mail has the best spam protection around
http://mail.yahoo.com

> The DDK docs say "The current IRQL is

saved in OldIrql. Then, the
current IRQL is reset to DISPATCH_LEVEL, and the
specified spin lock is
acquired." Which if accurate, means the old IRQL
value is stored BEFORE the
lock is acquired?

The DDK doc is definitely wrong here. Driver developer
must be able to deal with what the OS doesn’t do what
the DOC had said. This particular statement in DDK doc
is so logically broken. Don’t even need to look into
the assembly code to prove it wrong. I included it
here just in case.

hal!KeAcquireSpinLock:
806d2e92 8bff mov edi,edi
806d2e94 55 push ebp
806d2e95 8bec mov ebp,esp
806d2e97 8b4d08 mov ecx,[ebp+0x8]
806d2e9a e819c2ffff call
hal!KfAcquireSpinLock (806cf0b8) <<< spin and wait.
806d2e9f 8b4d0c mov ecx,[ebp+0xc]
806d2ea2 8801 mov [ecx],al <<<<<
update oldIRQL
806d2ea4 5d pop ebp
hal!KeAcquireSpinLock+0x13:
806d2ea5 c20800 ret 0x8

Your question on “lock bts” is irrelevant. I guess you
are off track.


Calvin Guan (Windows DDK MVP)
NetXtreme Longhorn Miniport Prime
Broadcom Corp. www.broadcom.com

So the question is, does the old IRQL variable
passed to KeAquireSpinLock
need to be unique to each caller or not? I looked at
the assembler for both
OS functions and it seems like the DDK documentation
isn’t correct for my
x86 processor, although perhaps on some processor it
is correct. For an x86,
the lock prefix in those OS functions should cause a
memory barrier, and
prevent the old IRQL from getting updated if the
lock isn’t acquired. The
semantics of the DDK docs is safer, although not
helping me determine if
this code is broken. The DDK docs don’t say the old
IRQL needs to be a
local, even though the description implies it does
to prevent the above
problem.

Opinions?

  • Jan

Questions? First check the Kernel Driver FAQ at
http://www.osronline.com/article.cfm?id=256

You are currently subscribed to ntdev as:
xxxxx@yahoo.ca
To unsubscribe send a blank email to
xxxxx@lists.osr.com


Find your next car at http://autos.yahoo.ca

In the DDK header files,the KeAcquireSpinLock is defined as the
following(X86):
#define KeAcquireSpinLock(a,b) *(b) = KfAcquireSpinLock(a)
So i suppose that the OldIrql is saved after the routine has acquired
the spinlock.
best regards,
hanzhu
Calvin Guan wrote:

>The DDK docs say “The current IRQL is
>saved in OldIrql. Then, the
>current IRQL is reset to DISPATCH_LEVEL, and the
>specified spin lock is
>acquired.” Which if accurate, means the old IRQL
>value is stored BEFORE the
>lock is acquired?
>
>

The DDK doc is definitely wrong here. Driver developer
must be able to deal with what the OS doesn’t do what
the DOC had said. This particular statement in DDK doc
is so logically broken. Don’t even need to look into
the assembly code to prove it wrong. I included it
here just in case.

hal!KeAcquireSpinLock:
806d2e92 8bff mov edi,edi
806d2e94 55 push ebp
806d2e95 8bec mov ebp,esp
806d2e97 8b4d08 mov ecx,[ebp+0x8]
806d2e9a e819c2ffff call
hal!KfAcquireSpinLock (806cf0b8) <<< spin and wait.
806d2e9f 8b4d0c mov ecx,[ebp+0xc]
806d2ea2 8801 mov [ecx],al <<<<<
update oldIRQL
806d2ea4 5d pop ebp
hal!KeAcquireSpinLock+0x13:
806d2ea5 c20800 ret 0x8

Your question on “lock bts” is irrelevant. I guess you
are off track.

> In the DDK header files,the KeAcquireSpinLock is defined as the

following(X86):
#define KeAcquireSpinLock(a,b) *(b) = KfAcquireSpinLock(a)
So i suppose that the OldIrql is saved after the routine has acquired
the spinlock.
best regards,

That’s curious BOTH functions seem to be defined in the HAL, on my W2k3 SP1
system:

hal!KeAcquireSpinLock:
80a5a32e 8bff mov edi,edi
80a5a330 55 push ebp
80a5a331 8bec mov ebp,esp
80a5a333 8b4d08 mov ecx,[ebp+0x8]
80a5a336 e8b5ccffff call hal!KfAcquireSpinLock (80a56ff0)
80a5a33b 8b4d0c mov ecx,[ebp+0xc]
80a5a33e 8801 mov [ecx],al
80a5a340 5d pop ebp
80a5a341 c20800 ret 0x8

> Your question on “lock bts” is irrelevant. I guess you

are off track.

I was thinking the lock prefix was needed to make the instruction be a write
fence, otherwise out of order execution could cause the write of the old
irql to be visible to other processors before the lock word is written.
Getting out the AMD processor manual… It looks like the write of the lock
word would always precede the write of the old irql (with or without the
lock prefix), even if the write instruction for the irql executed first. The
lock prefix is needed for its value as a read fence, as instructions could
execute out of order and access memory inside the spinlocked code before the
lock word is written.

  • Jan

The lock prefix is to insure cache coherence with multiple processors.

Loren

Both functions are there for backwards compatibility. KeAcquireSpinLock
was not always a #define to KfAcquireSpinLock, at one point in time it
was a true export. As such, it needs to remain a true export and exist
in both incarnations so that these older drivers will actually load.

d

-----Original Message-----
From: xxxxx@lists.osr.com
[mailto:xxxxx@lists.osr.com] On Behalf Of Jan Bottorff
Sent: Tuesday, October 18, 2005 11:31 PM
To: Windows System Software Devs Interest List
Subject: RE: [ntdev] Question on KeAquireSpinLock

In the DDK header files,the KeAcquireSpinLock is defined as the
following(X86):
#define KeAcquireSpinLock(a,b) *(b) = KfAcquireSpinLock(a)
So i suppose that the OldIrql is saved after the routine has acquired
the spinlock.
best regards,

That’s curious BOTH functions seem to be defined in the HAL, on my W2k3
SP1
system:

hal!KeAcquireSpinLock:
80a5a32e 8bff mov edi,edi
80a5a330 55 push ebp
80a5a331 8bec mov ebp,esp
80a5a333 8b4d08 mov ecx,[ebp+0x8]
80a5a336 e8b5ccffff call hal!KfAcquireSpinLock (80a56ff0)
80a5a33b 8b4d0c mov ecx,[ebp+0xc]
80a5a33e 8801 mov [ecx],al
80a5a340 5d pop ebp
80a5a341 c20800 ret 0x8


Questions? First check the Kernel Driver FAQ at
http://www.osronline.com/article.cfm?id=256

You are currently subscribed to ntdev as: xxxxx@microsoft.com
To unsubscribe send a blank email to xxxxx@lists.osr.com

> That’s curious BOTH functions seem to be defined in the HAL, on my W2k3
SP1

system:

hal!KeAcquireSpinLock:

This is because they change IRQL, and IRQL is implemented within HAL.

KeAcquireSpinLockFromDpcLevel is a kernel function, not HAL.

Maxim Shatskih, Windows DDK MVP
StorageCraft Corporation
xxxxx@storagecraft.com
http://www.storagecraft.com