Here's what I found, which is a good part of the answer.
The IP and most other registers are saved on the kernel stack "close" to where the stack is when your ISR is called.
In the AMD64 architecture, the data is saved in a KTRAP_FRAME structure, defined in ntddk.h. I did not find a similar definition on the X86 architecture.
The last few items of the trap frame are the data that is saved by the hardware when the interrupt is taken, and that includes the RIP to resume the interrupted thread.
The ISR that I have written is set up with IoConnectInterrupt(). It is recording what is nearby on the stack at the time. What I see is that between the trap frame and my ISR's stack are: a return address, 9 more QWORDs, and the ISR's return address.
What's missing from the picture is how to know there are those 10 extra QWORDs on the stack, and whether this is dependable. I suspect not from one OS release to another. I could have the ISR (the first time) try to verify where the trap frame is, by looking for certain things (like the CS and SS registers), then remember the offset for the future. I don't like doing that, certainly not in a commercial product.
I've seen some mention about interrupt hooks, which sounds different from IoConnectInterrupt(). Perhaps that's a way to get called directly from the first interrupt handler, so that it would be certain where the trap frame was located.
Anyway, I know there must be a reliable way of doing this, because CodeAnalyst is able to collect the RIPs.