rizinorg / cutter

Free and Open Source Reverse Engineering Platform powered by rizin
https://cutter.re
GNU General Public License v3.0
15.87k stars 1.15k forks source link

Redirect STDOUT to Console Widget for Python Plugins #1434

Open ITAYC0HEN opened 5 years ago

ITAYC0HEN commented 5 years ago

Is your feature request related to a problem? Please describe.

Currently, when Plugin author uses print("hello, world"), the output would be executed on the terminal which was executing Cutter (on windows usually there won't be a terminal for this).

This is unexpected behavior since we have a Console Widget

Describe the solution you'd like I want STDOUT to be redirected to the Console widget. In general, but specifically here for Python Plugins.

Additional context

related issue: #966

thestr4ng3r commented 5 years ago

Related: https://github.com/radare/radare2/pull/13622 (to not clutter the output as much)

pelijah commented 5 years ago

We have CutterCore::message() https://github.com/radareorg/cutter/blob/master/src/core/Cutter.cpp#L722

pelijah commented 5 years ago

So you can just use cutter.message() instead of print().

ITAYC0HEN commented 5 years ago

But people use print, which is okay, should be redirected to Cutter.Message

ITAYC0HEN commented 5 years ago

And there are more ways to generate output to STDOUT and STDERR in python (and any other thingies), not only prints. Even when python throws exception I'd expect it to be on Console widget and not on the executing Terminal (which not always exists)

pelijah commented 5 years ago

Python and plugins are initialized before MainWindow. So we have to change the order.

thestr4ng3r commented 5 years ago

We can have some kind of buffer for output received before the MainWindow has been started. There might be something interesting in it, even without Python Plugins.

gaasedelen commented 5 years ago

I agree that this should be the default. Existing interactive disassembler platforms already behave this way, so now all of your users will expect Cutter to behave the same way whether you like it or not 😅

Might be best not to overthink this. IDA has a pretty straightforward implementation that you can draw from for Cutter.

They hook the interpreter's stdout/stderr stream with a simple proxy object:

https://github.com/idapython/src/blob/15b9ab2535222e492cd21b8528c27f763fb799d6/python/init.py#L98-L101

The proxy simply passes along any file descriptor writes to idaapi.msg(), eg cutter.message():

https://github.com/idapython/src/blob/15b9ab2535222e492cd21b8528c27f763fb799d6/python/init.py#L59-L62

ITAYC0HEN commented 5 years ago

@gaasedelen sounds great! Want to have a look and try solve it? Actually, maybe this can be fixed globally somehow for the whole application https://github.com/radareorg/cutter/issues/966

thestr4ng3r commented 5 years ago

Yes, this should not be done in python. There are ways to replace/redirect the actual stderr/stdout fds.

yossizap commented 5 years ago

Working on this issue in PR #1821. If you have any additional suggestions or recommendations based on your experience please add them there.

ITAYC0HEN commented 5 years ago

The pull request https://github.com/radareorg/cutter/pull/1821 is not merged but we still did not solve the issue for python printings

segevfiner commented 2 years ago

Python (Well, 3 at least) just writes to the standard stdio fds, so if you redirect them, it should redirect its output as well (pytest does that for example). But note that Python checks whether the standard streams are a terminal or not to configure buffering, so you might want to add startup code to reconfigure them as needed.

Though under /SUBSYSTEM:WINDOWS it does set them to None.

segevfiner commented 2 years ago

This boils down to init order. At least on Windows, Python has a special hack when stdio is a console to use WriteConsoleW instead so that it handles unicode (AKA winconsoleio). As such if you initialize Python before setting up output redirection, it will initialize to using that. You can reinitialize the Python stdio like this (Python 3, it's a bit more complicated in 2 as far as I recall):

sys.stdin = open(0, "r", closefd=False)
sys.stdout = open(1, "w", closefd=False)
sys.stderr = open(2, "w", closefd=False)

(Or from C PyFile_FromFd, PySys_SetObject)

Which can be done before calling Python again after setting up output redirection. Alternatively, delay Python initialize until after setting up output redirection.

It's also worth considering the buffering mode set for the stdio streams in Python.

Also, Python 3.8 new initialization API also allows some control over this: https://docs.python.org/3/c-api/init_config.html

Test code ```python import sys import cutter class MyCutterPlugin(cutter.CutterPlugin): name = "My Plugin" description = "This plugin does awesome things!" version = "1.0" author = "1337 h4x0r" def setupPlugin(self): with open("output.txt", "w") as f: print(sys.stdout, file=f) print(sys.stderr, file=f) print(sys.__stdout__, file=f) print(sys.__stderr__, file=f) def setupInterface(self, main): with open("output.txt", "a") as f: print("=======", file=f) print(sys.stdout, file=f) print(sys.stderr, file=f) print(sys.stdout.buffer, file=f) print(sys.stderr.buffer, file=f) print(sys.stdout.buffer.raw, file=f) print(sys.stderr.buffer.raw, file=f) print(sys.__stdout__, file=f) print(sys.__stderr__, file=f) #print("Hello, World!", flush=True) # Exception sys.stdout = open(1, "w", closefd=False) sys.stderr = open(2, "w", closefd=False) print("Hello, World!", flush=True) print("Hello, World!", file=f) print(sys.stdout, file=f) print(sys.stdout.buffer, file=f) print(sys.stdout.buffer.raw, file=f) def terminate(self): pass def create_cutter_plugin(): return MyCutterPlugin() ```

P.S. I'm still wondering about the weird behavior when running from a console, which causes output to the console to freeze once setting up output redirection but then somehow continue after closing cutter... that's weird...