Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PyPi distribution #351

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

name: Upload Python Package

on:
workflow_dispatch:
release:
types: [published]

jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]

steps:
- uses: actions/checkout@v2

- name: Build wheels
uses: pypa/cibuildwheel@v2.13.0

- uses: actions/upload-artifact@v2
with:
path: ./wheelhouse/*.whl

build_sdist:
name: Build source distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Build sdist
run: pipx run build --sdist

- uses: actions/upload-artifact@v2
with:
path: dist/*.tar.gz

PyPI:
name: Upload to PyPI

needs: [build_wheels, build_sdist]
runs-on: ubuntu-latest
permissions:
id-token: write

steps:
- uses: actions/download-artifact@v2
with:
name: artifact
path: dist
- name: Publish package
uses: pypa/gh-action-pypi-publish@release/v1
34 changes: 34 additions & 0 deletions .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# This tries to build packages, and tests the packages.
# It runs on every push to branches following the pattern v*.*.*.
# It makes sure that everything will run when the version is released.

name: Test Build

on:
workflow_dispatch:
push:
branches:
- v*.*.*

jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]

steps:
- uses: actions/checkout@v2

- name: Build wheels
uses: pypa/cibuildwheel@v2.13.0

build_sdist:
name: Build source distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Build sdist
run: pipx run build --sdist
26 changes: 26 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
# IDE/Filesystem
.idea
.vscode
.DS_Store

*.swp
*.swo
*.gcno
*.gcda
*.kdev4
/.kdev4

# Python
__pycache__
*.egg-info
.venv
build/

# Build outputs
bytes/*.cpp
*.so
*.o
*.a

# Test artifacts
tests/*.tok.*
tests/*.src.*
tests/*.err
tests/tests

# CMake/Ninja artifacts
*.cmake
cmake-build-debug/
6 changes: 3 additions & 3 deletions ASTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3007,7 +3007,7 @@ void print_src(PycRef<ASTNode> node, PycModule* mod, std::ostream& pyc_output)
} else {
pyc_output << "\n";
start_line(cur_indent, pyc_output);
if (code_src->flags() & PycCode::CO_COROUTINE)
if (code_src->flags() & PycCode::CO_COROUTINE_)
pyc_output << "async ";
pyc_output << "def ";
print_src(dest, mod, pyc_output);
Expand Down Expand Up @@ -3039,12 +3039,12 @@ void print_src(PycRef<ASTNode> node, PycModule* mod, std::ostream& pyc_output)
}
}
}
if (code_src->flags() & PycCode::CO_VARARGS) {
if (code_src->flags() & PycCode::CO_VARARGS_) {
if (narg)
pyc_output << ", ";
pyc_output << "*" << code_src->getLocal(narg++)->value();
}
if (code_src->flags() & PycCode::CO_VARKEYWORDS) {
if (code_src->flags() & PycCode::CO_VARKEYWORDS_) {
if (narg)
pyc_output << ", ";
pyc_output << "**" << code_src->getLocal(narg++)->value();
Expand Down
21 changes: 17 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
project(pycdc)
cmake_minimum_required(VERSION 3.1)
cmake_minimum_required(VERSION 3.12)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Debug options.
option(ENABLE_BLOCK_DEBUG "Enable block debugging" OFF)
option(ENABLE_STACK_DEBUG "Enable stack debugging" OFF)
option(ENABLE_BINDINGS "Enable Python bindings" OFF)

# Turn debug defs on if they're enabled.
if (ENABLE_BLOCK_DEBUG)
Expand All @@ -16,8 +17,20 @@ if (ENABLE_STACK_DEBUG)
add_definitions(-DSTACK_DEBUG)
endif()

# For generating the bytes tables
find_package(PythonInterp REQUIRED)
# For generating the bytes tables and bindings
set(PYTHON_VENV_PATH "${CMAKE_SOURCE_DIR}/.venv")
if (EXISTS "${PYTHON_VENV_PATH}")
message("Using existing Python venv at ${PYTHON_VENV_PATH}")
set(Python_ROOT_DIR "${PYTHON_VENV_PATH}")
endif()

if (ENABLE_BINDINGS)
# -fPIC since bindings are built as a shared lib
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
add_subdirectory(bindings)
else()
find_package(Python REQUIRED Interpreter)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wshadow -Werror ${CMAKE_CXX_FLAGS}")
Expand All @@ -38,7 +51,7 @@ foreach(ver ${PYTHON_VERSIONS})
endforeach()

add_custom_command(OUTPUT ${MAP_SOURCES}
COMMAND ${PYTHON_EXECUTABLE}
COMMAND ${Python_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/bytes/comp_map.py
${CMAKE_CURRENT_SOURCE_DIR}/bytes
${CMAKE_CURRENT_BINARY_DIR}/bytes
Expand Down
43 changes: 43 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
![Tests](https://img.shields.io/github/actions/workflow/status/zrax/pycdc/linux-ci.yml?branch=master&label=tests&style=flat-square)
[![PyPI](https://img.shields.io/pypi/v/pycdc?color=blue&style=flat-square)](https://pypi.org/project/pycdc/)
[![Python](https://img.shields.io/pypi/pyversions/pycdc?color=blue&style=flat-square)](https://pypi.org/project/pycdc/)

# Decompyle++
***A Python Byte-code Disassembler/Decompiler***

Expand Down Expand Up @@ -28,6 +32,14 @@ https://github.com/zrax/pycdc
* For makefiles, just run `make`
* To run tests (on \*nix or MSYS), run `make check`

## Building and installing the Python package

This step does not require building the executables of the previous sections.

* Ensure `CMake >= 3.12` is installed
* Create a virtual environment `python3 -m venv venv`
* Run `pip install .`

## Usage
**To run pycdas**, the PYC Disassembler:
`./pycdas [PATH TO PYC FILE]`
Expand All @@ -43,6 +55,37 @@ Both tools support Python marshalled code objects, as output from `marshal.dumps

To use this feature, specify `-c -v <version>` on the command line - the version must be specified as the objects themselves do not contain version metadata.

**To use the Python bindings**

Ensure the `pycdc` Python package is installed:

- either from source (see [above](#building-and-installing-the-python-package))
- or from PyPi : `pip install pycdc`

Then, use it as below:

```python
import marshal
from pycdc import decompyle

async def test():
a = 5
data = foobar(a)
return data

print(decompyle(marshal.dumps(test.__code__)))
```

or from a `.pyc` file:

```python
from pycdc import decompyle

with open('test.pyc', 'rb') as f:
# pass version=None to infer from the file, or specify a version tuple
print(decompyle(f.read(), version=None))
```

## Authors, Licence, Credits
Decompyle++ is the work of Michael Hansen and Darryl Pogue.

Expand Down
28 changes: 28 additions & 0 deletions bindings/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Find the interpreter as well for byte files generation
find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)

# Find pybind11
execute_process(
COMMAND ${Python_EXECUTABLE} -c "import pybind11; print(pybind11.get_cmake_dir(), end='')"
OUTPUT_VARIABLE pybind11_DIR
)
find_package(pybind11 CONFIG REQUIRED)

# Create C library
pybind11_add_module(bindings
bindings.cpp
../pycdc.cpp
../ASTree.cpp
../ASTNode.cpp
)

target_include_directories(bindings PRIVATE pybind11::headers ${Python_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR})
target_link_libraries(bindings PRIVATE pycxx)

if (NOT DEFINED CMAKE_LIBRARY_OUTPUT_DIRECTORY)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/build/lib")
endif ()

target_compile_definitions(
bindings
PRIVATE VERSION_INFO=${EXAMPLE_VERSION_INFO})
24 changes: 24 additions & 0 deletions bindings/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import sys

from .bindings import decompyle as _decompyle

__version__ = '0.0.1'


def decompyle(code, version=(sys.version_info.major, sys.version_info.minor)):
"""
Decompyle the given code object.

Parameters
----------
code : bytes
The code object to decompile.
version : tuple, optional
The Python version to decompile for. Defaults to the current Python version.
Use None or (0, 0) to infer the Python version from the code object. This will
not work for marshalled code objects.
"""
if version is None:
return _decompyle(code, 0, 0)
else:
return _decompyle(code, version[0], version[1])
49 changes: 49 additions & 0 deletions bindings/bindings.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#pragma clang diagnostic push
#pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions"

#include <Python.h>
#include <pybind11/pybind11.h>
#include <pybind11/pytypes.h>
#include <vector>
#include <cstring>
#include <ostream>
#include <sstream>
#include <optional>
#include "ASTree.h"

namespace py = pybind11;


#ifdef WIN32
# define PATHSEP '\\'
#else
# define PATHSEP '/'
#endif

py::str decompyle_binding(py::bytes &data, int major_version, int minor_version) {
PycModule mod;
auto str = data.cast<std::string>();
PycBuffer buffer(
reinterpret_cast<const unsigned char*>(str.c_str()),
str.size()
);

if (major_version == 0 && minor_version == 0) {
mod.loadFromStream(buffer);
}
else {
mod.loadFromMarshalledStream(
buffer,
major_version,
minor_version
);
}
std::ostringstream pyc_output;
decompyle(mod.code(), &mod, pyc_output);
return pyc_output.str();
}

PYBIND11_MODULE(bindings, m) {
m.doc() = "pycdcpy bindings";
m.def("decompyle", &decompyle_binding, "Decompile a marshalled python file");
}
2 changes: 2 additions & 0 deletions data.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "data.h"
#include <cstring>
#include <cstdarg>
#include <ostream>
#include <vector>

/* PycData */
Expand Down Expand Up @@ -80,6 +81,7 @@ int PycBuffer::getBuffer(int bytes, void* buffer)
bytes = m_size - m_pos;
if (bytes != 0)
memcpy(buffer, (m_buffer + m_pos), bytes);
m_pos += bytes;
return bytes;
}

Expand Down
Loading