There are multiple problems.
Here
ipp:
pop dx
print dx
ret
you remove the return address from the stack with pop dx. Where do you think ret will return after you steal its return address? Why don't you put the address back to the stack?
Also, with that same pop dx you corrupt the dx register value of the program that gets interrupted by your ISR. Why don't you save and restore dx in the ISR?
Yet another one... I don't know your assembler, but this line
lea dx, #1
looks odd. When you invoke print dx this line of the macro, IMO, should transform into
lea dx, dx
and that is an invalid x86 instruction. Does your code compile at all??? If it does, what does your assembler do with lea dx, dx? Does it make it mov dx, dx or nop or does it remove/ignore it?
What's more, function 9 of int 21h displays a "$"-terminated ASCII string whose address is in ds:dx and you don't construct any such string in the code.
Your code never sets up ds and just uses whatever ds value happens to be in the interrupted program, which is bad. You need to save and restore ds if you want to use it. And if your ISR's data is going to be contained in the code segment of the ISR, you want to load ds with the value of cs.
And then dx has the offset of iret before print dx. Are you sure you want to print that value of ip? When do you want to know the value of ip and where?
Lastly, why do you have an ISR for all this?
Why don't you first make your ipp work without an ISR? And why don't you use a debugger?