Как изменить исходный код программы

Читайте, как изменить код программы, если у вас нет её исходного кода. Объясняем, как это работает и помогаем выбрать для себя один из самых распространенных способов.

Иногда случаются такие ситуации, когда нужно внести изменения в программу, но исходный код отсутствует. Как тогда изменить код программы?

Решение воздействовать на код программы в отсутствие ее исходного кода случается довольно часто и в основном этому предшествуют 2 основные причины:

  • вы потеряли исходный код;
  • его у вас никогда и не было, но хотите доработать какую-то программу.

Итак, есть скомпилированный файл, но нет исходного кода, как тогда изменить код программы? На помощь может прийти процесс декомпиляции. Почему «может»? Потому что в некоторых случаях и этот процесс бессилен. Плюс, возможно, вы хотите изменить исходный код программы, который специально защищен. В таком случае программы-декомпиляторы могут оказаться бессильными. Помимо этого, многое зависит от конкретного декомпилятора и языка программирования, на котором написана ваша программа. В некоторых случаях декомпилятор выдает исходный код, в котором вы не сможете найти нужный участок кода для его изменения.

В общем, «в таком положении» без отсутствия исходного кода вероятность, что что-то получится, невысокая, но она есть. А раз есть вероятность, значит, можно пробовать.

 

Как изменить код программы без исходников?

Первым в этом случае вам может помочь процесс декомпиляции. Декомпиляция — это способ создания исходного кода программного обеспечения на том языке, на котором оно было разработано. То есть компиляция — это когда исходный код программного обеспечения преобразуется в машинный код, а декомпиляция — это обратный процесс.

Декомпиляция в некоторых случаях позволяет изменить код программы без исходников, однако нужно понимать, что полностью восстановить исходный код программы может не получиться. Декомпилятор вообще может вам выдать непонятный результат, и вы просто не будете знать, что с этим результатом делать. На практике получается, что чем «меньше» программа, тем качественней проходит процесс декомпиляции, и наоборот — высоконагруженные программы тяжело декомпилируются.

Декомпиляцию возможно осуществить при помощи специализированных программ:

  1. VB Decompiler;
  2. ReFox;
  3. DeDe;
  4. EMS Source Rescuer;
  5. PEID;
  6. и др.

Если вам известно, на каком языке была разработана программа, то нужно искать декомпилятор именно под этот язык программирования. Если же вам неизвестно, на каком языке был разработан исходный код программы, тогда вам потребуется попробовать несколько декомпиляторов, чтобы найти нужный.

Суть декомпиляции сводится к следующему: вы из машинного кода программы пытаетесь восстановить ее исходный код, в котором будете вносить необходимые изменения. Это самый простой метод, но он работает не всегда. Есть еще один альтернативный метод, как изменить исходный код программы без исходника, но он требует специальной подготовки.

 

Как изменить код программы без декомпиляции?

В качестве альтернативного метода, как изменить код программы без исходника, выступает изучение ассемблера, в частности раздела «реверс-инженерия», чтобы уметь воздействовать на машинный код. Да, это сложно, так как потребует долгого и упорного изучения, но это будет самым верным подходом. Ведь в этом случае вы будете знать, на какой байт вам нужно воздействовать, чтобы произвести изменения в машинном коде программе. На сайте SpyLife вы сможете найти топ-3 программных обеспечений, позволяющее получать всю необходимую информацию со стороннего устройства.

В качестве вспомогательного инструмента в этом случае будут программы OllyDbg и Ida PRO.

 

Заключение

Изменить код программы без исходника возможно только одним из описанных выше способов:

  • используя декомпилятор;
  • изучая ассемблер.

Кстати, декомпилятор может помочь узнать, на каком языке написан исходный код. Принцип распознавания прост: к какому языку программирования относится декомпилятор, который сможет обработать загружаемый код, к тому языку и будет относиться исходный код программы.

Здравствуйте! Только что задался вопросом: можно ли изменить код уже скомпилированной программы(так чтобы она работала)? То есть, имея исполняемый файл, можно ли его открыть как текстовый документ и изменять(ну всё-таки исходные коды линковщиков и компиляторов есть же). Я понимаю, что после работы компилятора, си там уже не пахнет, но всё же, изучив структуру ехе файла — это можно сделать, или это полный бред?

задан 28 апр 2017 в 20:26

Stdugnd4ikbd's user avatar

1

Полностью исходный код программы конечно получить не возможно, но есть декомпиляторы которые его стараются восстановить, но получается из этого вырви-глаз и разбирать там не чего, а тем более редактировать. Насчёт редактирования ПО, это вам нужно изучить ассемблер (советую гуглить по запросу «реверс инжиниринг») и научиться таким программам как OllyDbg либо IdaPRO (платная и достаточно дороговатая) и потом уже делать патчи на те программы которые вы хотите, точнее редактировать. Но есть проблема в вашем вопросе, как вы хотите её редактировать, если просто дизайн, то вам достаточно и редактора ресурсов, которых OVER 9999+ в интернете как бесплатных(Resource Hacker), так и платных(Resource Tuner).

ответ дан 28 апр 2017 в 20:31

alex-rudenkiy's user avatar

alex-rudenkiyalex-rudenkiy

4,0122 золотых знака17 серебряных знаков34 бронзовых знака

13

Через конкретно блокнот (или там Word) — нет, запорет он вам некоторые символы…
А вот hex-редактором — в принципе можно. А как, по-вашему, всякие ломалки работают? :) Именно так — меняя в нужных местах код/данные.
Только тут — как в том апокрифе со старшим Капицей, которому якобы обещали за границей 10000 марок за ремонт какой-то там установки. Он приехал, посмотрел, сказал ассистенту ударить молотком в таком-то месте — все заработало. За такую работу принимающей стороне сумма показалась слишком большой, попросили счет. Он выглядел так:

Удар молотком — 1 марка.
За то что знал, где ударить — 9999 марок.

Словом, чтоб знать, куда ударить и какие байты и как поменять — надо долго и упорно учиться :)

Поменять какие-то данные типа, чтоб не Hello world выводила, а типа Coolhacker :) — это попроще…

ответ дан 29 апр 2017 в 5:09

Harry's user avatar

HarryHarry

210k15 золотых знаков115 серебряных знаков224 бронзовых знака

Как изменить код программы, если потерян исходник

Бывают ситуации, когда под рукой нет исходника, а Вам срочно требуется внести изменения в коде, написанной ранее Вами программы. Например, дано приложение «TextEdit.exe» — текстовый редактор, написанный на языке c#, который имеет простой пользовательский интерфейс, состоящий из двух кнопок и текстового поля.

28381

При нажатии на кнопку “Чтение” из файла 1.txt считываются и выводятся все строки в окно элемента управления textBox, а при нажатии на кнопку “Запись” данные из текстового поля сохраняются в файле 2.txt

Для хранения путей в программе используются две текстовых переменных: filePathIn и filePathOut

28382

Прошёл год, как программа была написана и отдана заказчику, но вдруг ему потребовалось изменить имя папки, в которой должны храниться оба файла, c 123 на Text. Задача простая, но прошло уже много времени, и исходник был потерян, что делать в такой ситуации?

Для начала вспомним, как образуется .NET сборка

28383

Полученный в результате компиляции файл (сборка) содержит внутри себя метаданные, манифест, код на языке IL (MSIL).

Метаданные — описывают типы данных и их члены

Манифест описывают саму сборку

MSIL код, полученный в результате компиляции файла исходного кода

То есть перед вами тот же исходник, только в другом формате. И для того, чтобы поработать с ним, Вам понадобиться специальный инструмент, который позволяет просматривать и редактировать данные внутри сборки.

Дизассемблер ILDASM

Данный инструмент входит состав пакета .Net Framework SDK, который является бесплатным и устанавливается вместе с Visual Studio (включая Express версию). С помощью него вы можете, как просматривать внутреннее содержимое сборки, так и изменять его.

Для удобства работы создадим отдельную папку, например: ”c:newasm” и поместим в неё файл TextEdit.exe

28384

Затем в меню “Пуск” открываем папку: «Visual Studio Tools»

28385

Запускаем командную строку разработчика

28386

Откроется консоль, вводим первую команду: ildasm. Для выполнения команды нажмите клавишу Enter.

28387

Появиться главное окно программы.

28388

Переместим файл сборку TextEdit.exe в окно дизассемблера ILASM, в результате отобразиться её внутреннее содержимое.

28389

Убедимся, что в ней содержатся нужные нам данные (пути к файлам).

28390

Два поля на месте, теперь взглянем на метаданные.

28391

Для доступа к метаданным Вы так же можно использовать сочетание горячих клавиш: Ctrl+M. Затем с помощью кнопки Find, найдём имя одного из файлов.

28393

28394

28395

Как видно присутствуют оба. Пути найдены, и теперь их нужно изменить, но все данные, в текущий момент, доступны только для просмотра, и изменить их в самой дизассемблере нельзя, поэтому воспользуемся второй возможностью данной программы и выгрузим содержимое сборки в файл.

Выгрузка данных

Выберите пункт меню File -> Dump

28396

Появится меню. В данном примере, все пункты меню оставим без изменений и просто нажмём кнопку OK.

28397

Появится диалоговое окно

28398

Выберем ранее созданную папку “newasm”, затем укажем имя и тип файла и нажмём на кнопку “Сохранить”

28399

В результате в папке “newasm” должно появиться несколько новых файлов

28400

Закрываем окно ildasm, а так же удаляем файл Textedit.exe, больше он нам не понадобиться. Теперь нас интересует полученный файл texted.il и для начала откроем его любым текстовым редактором, например блокнотом.

28401

Снова воспользуемся поиском (Ctrl+F)

28402

Так же видим найденные строки, которые содержат пути к файлам.

28403

Изменим текущее имя папки 123 на новое название Text, для обоих файлов

28404

Сохраняем внесённые изменения и закрываем блокнот.

Ассемблер ILASM

Изменения внесены и теперь нужно преобразовать файл txted.il обратно в исполняемый файл (.exe) Для этого нам понабиться второй инструмент ILASM, ассемблер, который так же входит в состав пакета SDK и не требует отдельной установки.

Код:

файл (.il) -> компилятор ilasm =  сборка (.exe и .dll)

Возвращаемся в консоль

28408

Вводим вторую команду:

Код:

ilasm /exe c:newasmtxted.il /output=c:newasmtextEdit.exe

Первый параметр: /exe — указывает компилятору, что на выходе мы хотим получить файл с расширением .exe. Затем указываем файл, который содержит MSIL-код. С помощью второго параметра /output — указываем имя и расширение нового файла.

28406

Если компиляции прошла успешно, то в окне консоли вы должны увидеть сообщение, которое выделено на картинке, а внутри папке “newasm” должен появиться новый файл TextEdit.exe, который теперь уже содержит новые пути к файлам.

28407

Вот таким не сложным способом можно выйти из данной ситуации, при этом не имея исходника под рукой.

Читайте также:

  • Cоздание файла dll
  • Программное выравнивание текста по ширине
  • Как удалить параметр реестра Windows

Принципы в заметке общие для почти любого языка программирования и системы исполнения, но акцент будет на jvm. Рассмотрим два основных подхода по модификации программы:

  • манипуляции с исполняемым кодом программы после компиляции или во время загрузки кода;
  • изменение исходного кода перед компиляцией.

Метафора, связанная с изображением в заметке: программа — это основное здание, а результат трансформации программы — вспомогательная конструкция.

Зачем модифицировать?

Начнем с вопроса зачем программе модифицировать другую программу. Метапрограммирование помогает уменьшить объем boilerplate кода в проекте и концентрироваться на главном, улучшить читаемость кода и решить задачи которые сложно решить другим способом.

Простейший пример в java — JavaBeans и get/set методы для доступа к полям класса, также примером может служить создание билдеров для класса, автореализации equals/hash в IDE и т.п.

Следующий пример — это логирование, автоматическое управление транзакциями. Все к чему привыкли при использовании Spring Framework и редко задумываемся как это реализовано. Но даже Spring создал сложности с конфигурацией и инициализацией фреймворка для новичков, что послужило причиной появления «магического» Spring Boot/Spring Roo. Но это отдельная тема, мы же вернемся к теме модификации программы.

Обфускация и минимизация кода, инструментирующие профилировщики и мир DevOps третий пример для чего нужно модифицировать программу. Думаю, что пропустил другие важные примеры, вы можете дополнить в комментариях.

Итак, зачем модифицировать программу мы определились, теперь рассмотрим, как это обычно делают.

Модификация исполняемых инструкций программы

Можно модифицировать байт-код программы и это самый распространенный способ. Делать это можно как сразу после компиляции, но перед сборкой jar, так и при загрузке класса. В первом случае это будет плагин системы сборки проекта, во втором специальный загрузчик классов или java агент, либо механизм hotswap в jvm. Подход с агентами и загрузчиками классов очень похож на самомодифицирующиеся программы в машинном коде и полиморфные вирусы.

Байт-код файла, который загружает jvm имеет структуру, описанную в официальной документации о формате класса
Приложение javap позволяет просматривать байт-код скомпилированного класса

Пример из официальной документации

Исходный текст класса:

import java.awt.*;
import java.applet.*;

public class DocFooter extends Applet {
        String date;
        String email;

        public void init() {
                resize(500,100);
                date = getParameter("LAST_UPDATED");
                email = getParameter("EMAIL");
        }

        public void paint(Graphics g) {
                g.drawString(date + " by ",100, 15);
                g.drawString(email,290,15);
        }
}

Вывод javap на консоль для байт-кода этого класса:

Compiled from "DocFooter.java"
public class DocFooter extends java.applet.Applet {
  java.lang.String date;

  java.lang.String email;

  public DocFooter();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/applet/Applet."<init>":()V
       4: return        

  public void init();
    Code:
       0: aload_0       
       1: sipush        500
       4: bipush        100
       6: invokevirtual #2                  // Method resize:(II)V
       9: aload_0       
      10: aload_0       
      11: ldc           #3                  // String LAST_UPDATED
      13: invokevirtual #4                  // Method getParameter:(Ljava/lang/String;)Ljava/lang/String;
      16: putfield      #5                  // Field date:Ljava/lang/String;
      19: aload_0       
      20: aload_0       
      21: ldc           #6                  // String EMAIL
      23: invokevirtual #4                  // Method getParameter:(Ljava/lang/String;)Ljava/lang/String;
      26: putfield      #7                  // Field email:Ljava/lang/String;
      29: return        

  public void paint(java.awt.Graphics);
    Code:
       0: aload_1       
       1: new           #8                  // class java/lang/StringBuilder
       4: dup           
       5: invokespecial #9                  // Method java/lang/StringBuilder."<init>":()V
       8: aload_0       
       9: getfield      #5                  // Field date:Ljava/lang/String;
      12: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: ldc           #11                 // String  by 
      17: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      20: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      23: bipush        100
      25: bipush        15
      27: invokevirtual #13                 // Method java/awt/Graphics.drawString:(Ljava/lang/String;II)V
      30: aload_1       
      31: aload_0       
      32: getfield      #7                  // Field email:Ljava/lang/String;
      35: sipush        290
      38: bipush        15
      40: invokevirtual #13                 // Method java/awt/Graphics.drawString:(Ljava/lang/String;II)V
      43: return        
}

Но модифицировать байт-код вручную имеет смысл только в учебных целях или если вы ниндзя, который любит создавать и преодолевать сложности. The Java Virtual Machine Specification отвечает на большинство вопросов на этом этапе.

В промышленном программировании работу с байт-кодом упрощают библиотеки ASM, javassist, BCEL, CGLIB. После парсинга байт кода, тот же ASM позволяет программисту работать с байт кодом как через Tree API, так и в событийной модели, используя шаблон visitor. Кроме анализа, возможна и модификация, добавление новых инструкций, полей, методов и т.п. В природе существуют и другие библиотеки работы с байт кодом, но их используют реже.

Пример использования API ASM для

анализа байт-кода

/***
 * ASM examples: examples showing how ASM can be used
 * Copyright (c) 2000-2011 INRIA, France Telecom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.BasicValue;
import org.objectweb.asm.tree.analysis.BasicVerifier;
import org.objectweb.asm.tree.analysis.Frame;
import org.objectweb.asm.tree.analysis.SourceInterpreter;
import org.objectweb.asm.tree.analysis.SourceValue;
import org.objectweb.asm.util.TraceMethodVisitor;
import org.objectweb.asm.util.Textifier;

/**
 * @author Eric Bruneton
 */
public class Analysis implements Opcodes {

    public static void main(final String[] args) throws Exception {
        ClassReader cr = new ClassReader("Analysis");
        ClassNode cn = new ClassNode();
        cr.accept(cn, ClassReader.SKIP_DEBUG);

        List<MethodNode> methods = cn.methods;
        for (int i = 0; i < methods.size(); ++i) {
            MethodNode method = methods.get(i);
            if (method.instructions.size() > 0) {
                if (!analyze(cn, method)) {
                    Analyzer<?> a = new Analyzer<BasicValue>(
                            new BasicVerifier());
                    try {
                        a.analyze(cn.name, method);
                    } catch (Exception ignored) {
                    }
                    final Frame<?>[] frames = a.getFrames();

                    Textifier t = new Textifier() {
                        @Override
                        public void visitMaxs(final int maxStack,
                                final int maxLocals) {
                            for (int i = 0; i < text.size(); ++i) {
                                StringBuilder s = new StringBuilder(
                                        frames[i] == null ? "null"
                                                : frames[i].toString());
                                while (s.length() < Math.max(20, maxStack
                                        + maxLocals + 1)) {
                                    s.append(' ');
                                }
                                System.err.print(Integer.toString(i + 1000)
                                        .substring(1)
                                        + " "
                                        + s
                                        + " : "
                                        + text.get(i));
                            }
                            System.err.println();
                        }
                    };
                    MethodVisitor mv = new TraceMethodVisitor(t);
                    for (int j = 0; j < method.instructions.size(); ++j) {
                        Object insn = method.instructions.get(j);
                        ((AbstractInsnNode) insn).accept(mv);
                    }
                    mv.visitMaxs(0, 0);
                }
            }
        }
    }

    /*
     * Detects unused xSTORE instructions, i.e. xSTORE instructions without at
     * least one xLOAD corresponding instruction in their successor instructions
     * (in the control flow graph).
     */
    public static boolean analyze(final ClassNode c, final MethodNode m)
            throws Exception {
        Analyzer<SourceValue> a = new Analyzer<SourceValue>(
                new SourceInterpreter());
        Frame<SourceValue>[] frames = a.analyze(c.name, m);

        // for each xLOAD instruction, we find the xSTORE instructions that can
        // produce the value loaded by this instruction, and we put them in
        // 'stores'
        Set<AbstractInsnNode> stores = new HashSet<AbstractInsnNode>();
        for (int i = 0; i < m.instructions.size(); ++i) {
            AbstractInsnNode insn = m.instructions.get(i);
            int opcode = insn.getOpcode();
            if ((opcode >= ILOAD && opcode <= ALOAD) || opcode == IINC) {
                int var = opcode == IINC ? ((IincInsnNode) insn).var
                        : ((VarInsnNode) insn).var;
                Frame<SourceValue> f = frames[i];
                if (f != null) {
                    Set<AbstractInsnNode> s = f.getLocal(var).insns;
                    Iterator<AbstractInsnNode> j = s.iterator();
                    while (j.hasNext()) {
                        insn = j.next();
                        if (insn instanceof VarInsnNode) {
                            stores.add(insn);
                        }
                    }
                }
            }
        }

        // we then find all the xSTORE instructions that are not in 'stores'
        boolean ok = true;
        for (int i = 0; i < m.instructions.size(); ++i) {
            AbstractInsnNode insn = m.instructions.get(i);
            int opcode = insn.getOpcode();
            if (opcode >= ISTORE && opcode <= ASTORE) {
                if (!stores.contains(insn)) {
                    ok = false;
                    System.err.println("method " + m.name + ", instruction "
                            + i + ": useless store instruction");
                }
            }
        }
        return ok;
    }

    /*
     * Test for the above method, with three useless xSTORE instructions.
     */
    public int test(int i, int j) {
        i = i + 1; // ok, because i can be read after this point

        if (j == 0) {
            j = 1; // useless
        } else {
            try {
                j = j - 1; // ok, because j can be accessed in the catch
                int k = 0;
                if (i > 0) {
                    k = i - 1;
                }
                return k;
            } catch (Exception e) { // useless ASTORE (e is never used)
                j = j + 1; // useless
            }
        }

        return 0;
    }
}

Аспектно-ориентированный подход можно считать высокоуровневым способом модификации программы. В реализации AspectJ на уровне агента, загрузчика классов или плагина вся «магия» АОП превращается в манипуляции с байт-кодом классов. Но как это видит программист при разработке отличается от того как модифицируется байт код «под капотом» с помощью того же ASM и BCEL. Если интересно, что фактически добавляет AspectJ в классы вашего приложения, можно включить дамп модифицированных классов и по локоть влезть в этот код, например, с помощью Java Decompiler.

В AspectJ разработчик определяет действия в виде классов, аннотируя их как аспекты и указывая в каких точках программы(Pointcut) их следует вызывать. Синтаксис определения pointcut выражений также достаточно высокоуровневый. Такой подход к модификации байт-кода более прост в использовании для программиста.

Подробнее показывал и рассказывал на примерах в цикле публикаций на хабре

Трансформация программы за счет модификации байт-кода имеет свои сильные и слабые стороны:

Плюсы Минусы
подход работает при отсутствии исходных текстов программы сложность анализа и модификации, при нетривиальных трансформациях
независимость от компилятора отсутствие информации, доступной только в исходном коде

Трансформация AST исходного кода, метапрограммирование

Теория и практика трансформации исходного кода давно применяется в метапрограммировании, Prolog, Lisp, макросах и препроцессорах языков программирования.

При данном подходе исходный текст программы трансформируется либо дополняется другой программой перед компиляцией, а затем компилируется. Работать удобнее не с самим текстом программы, а с абстрактным синтаксическим деревом построенным из него (abstract syntax tree, AST).

Опять же, удобство метапрограммирования зависит от поддержки его в самом языке программирования. Существует шутка

В Лиспе, если охота аспектно-ориентированного программирования, нужно лишь настругать немного макросов, и готово. В Java, нужен Грегор Кичалес, создающий новую фирму, и месяцы и годы попыток заставить всё работать.

Петер Норвиг

Поэтому в jvm чуть сложнее, хоть механизм reflection и является частью языка и платформа может динамически загружать и исполнять байт-код. Сразу на ум приходят две технологии, которые используют кодогенерацию — JPA static metamodel generator и jaxb code generation. Другой пример — project Lombok, который позволяет автоматически реализовать, то что раньше генерировалось IDE или писалось вручную и поддерживалось разработчиками.

Аннотации проекта Lombok

val
Finally! Hassle-free final local variables.
@ NonNull
or: How I learned to stop worrying and love the NullPointerException.
@ Cleanup
Automatic resource management: Call your close() methods safely with no hassle.
@ Getter / @ Setter
Never write public int getFoo() {return foo;} again.
@ ToString
No need to start a debugger to see your fields: Just let lombok generate a toString for you!
@ EqualsAndHashCode
Equality made easy: Generates hashCode and equals implementations from the fields of your object.
@ NoArgsConstructor, @ RequiredArgsConstructor and @ AllArgsConstructor
Constructors made to order: Generates constructors that take no arguments, one argument per final / non-null field, or one argument for every field.
@ Data
All together now: A shortcut for @ ToString, @ EqualsAndHashCode, @ Getter on all fields, and @ Setter on all non-final fields, and @ RequiredArgsConstructor!
@ Value
Immutable classes made very easy.
@ Builder
… and Bob’s your uncle: No-hassle fancy-pants APIs for object creation!
@ SneakyThrows
To boldly throw checked exceptions where no one has thrown them before!
@ Synchronized
synchronized done right: Don’t expose your locks.
@ Getter(lazy=true)
Laziness is a virtue!
@ Log
Captain’s Log, stardate 24435.7: «What was that line again?»

Реализовано в Lombok это с помощью модификации AST пользовательской программы и кодогенерации.

Схожая функциональность, со своими ограничениями, есть и в java для аннотаций с областью видимости compile time — Annotation Processing Tool.

В случае с парсингом java исходного кода, лучше чем javac и eclipse java compiller вряд ли кто справится. Есть альтернативы, такие как Spoon и JTransformer, но насколько полно они поддерживают спецификацию и сложные классы, даже нет желания проверять.

Раз уж речь идет о jvm, то трансформация исходного текста программы на Groovy является частью самого языка, подобные возможности есть и в языке Scala.

Итак, в модификации исходного кода программы есть слабые и сильные стороны:

Плюсы Минусы
большее количество информации, чем в байт-коде этап компиляции или интерпретации (память, время)
возможности, подобные рефакторингу в IDE требование к наличию исходных текстов, способа автоматически найти их для заданного класса/jar

Трансформация AST кода и рекомпиляция во время выполнения

Самая хардкорная часть этой заметки — мысли про перекомпиляцию в рантайм. Модификация и компиляция AST java кода в момент выполнения может быть нужна, если требуются костыли: проект либо полный капролит, либо делать и поддерживать десятки форков разных версий очень трудоемко, либо если его менеджмент и разработчики считают его идеальным и не позволяют никому его модификацию, но при этом исходный текст есть в enterprise maven репозитарии. И этот подход нужен только если задачу невозможно или неудобно решать двумя ранее описанными классами трансформации программы.

С компиляцией все относительно просто. JavaCompiler API позволяет скомпилировать программу из исходного кода в момент выполнения, предоставляя интерфейс независимый от реализации. При изучении манифеста и исходных текстов eclipse EJC компилятора, обнаружил что и он поддерживает JavaCompiler API.

Но при анализе текста программы все равно нет публичного и универсального API для работы с AST. Т.е. придется работать либо с com.sun.source.tree.* либо org.eclipse.jdt.core.dom.*

Задача с поиском исходного текста класса легко решается, если проект публиковался в maven репозитарий вместе с артефактом типа source и в jar с классами есть файлы pom.properties или pom.xml, либо есть некий словарь соответствия названия/хеша артефакта исходному коду соответствующего jar файла и способ получить эти исходники во время работы программы.

Плюсы — для трансформации доступно больше информации, чем есть в байт-коде, применение не требует пересборки проекта и почти так же удобно как и применение AspectJ агента, но при этом трансформацию невозможно было выполнить средствами преобразования байт-кода, либо очень трудоемко.

Минусы такие же, как и у прошлого подхода: память, время, требование к наличию исходного текста программы и способа его найти для данного класса.

Примеры вышесказанного в виде кода ejc+maven будут в ближайшие месяцы, да и задача выбрана вполне жизненная. Сталкивались ли вы с подобным? Какие задачи из вашей практики можно было бы элегантно решить только с помощью трансформации java кода и рекомпиляции во время выполнения?

Кстати, возможности компилятора TinyCC и его размер доказывают, что такой подход возможен и для C программ.

В заметке мы рассмотрели несколько подходов по модификации программы, их сильные и слабые стороны. Модификация исполняемого кода более распространена, но не все задачи можно решить без наличия исходного кода программы и его последующей трансформации.

I have a small executable that I downloaded from the net, and that runs in the Command Line, which makes me think it may be a DOS program. The program works perfectly, but due to being developed by a non-English speaker, the interface/presentation of it needs to be cleaned up to make it look a little more professional. Is it possible to get to the file’s source code and edit it?

asked Jan 1, 2016 at 20:05

Hashim Aziz's user avatar

1

Principally: Yes.
But: it’s not practically.

You could change the machine-code within the .exe, but that’s not really practically if you want to change the interface/presentation. Also you would need to do it at least in assembly language.
If you want to improve the software, I think, the best would be to contact the developer and ask if you can help him to improve the software.

If you want to take a look inside the .exe anyway you could try OllyDbg.

If you want to change something in such an existing program, you have basically 3 ways to do it:

  1. Changing the machine code itself:
    The representation of the machine code does not need to be 1’s and 0’s, you can use any other number system. Anyway machine code is really, really hard to read and edit. Additionally you have also the same problems like you ‘simply’ disassemble the program.

  2. Use a disassembler and modify the assembler code:
    You have to take care of the memory layout of the software you want to modify. Since any change on some string could override another variable. And assembler is also not easy to read and write. So depending on the size of the software, it takes some time to get a basic overview about where to change the code.

  3. Or use a decompiler:
    But you mostly won’t get any useful variable names, since the compiler removes them most of the time.

Another point you have to take a look at is anti debugging and reverse engineering tools. They may prevent from running the piece of code inside debugger like OllyDbg or getting useful decompiled code.
Also take a look at peter ferrie’s answer.

Community's user avatar

answered Jan 1, 2016 at 20:28

falx's user avatar

falxfalx

664 bronze badges

4

The short answer is no — the source is not available if only the .exe is available. The source code is an entirely separate file which is generally not shared with the public. However, given the .exe file, it might be possible to «decompile» it into a form of source code which would allow a new .exe file to be produced, and which should match the existing one fairly well when performing a byte-for-byte comparison.

With that decompiled source code in hand, it would be possible to make modifications to the behavior or appearance of the program, but it would be far from trivial, since such relatively important things as variable names will not be present, so deriving the meaning of certain memory accesses will require a lot of time and effort.

You would need to consider carefully if the effort is worth the reward.

answered Jan 2, 2016 at 5:18

peter ferrie's user avatar

peter ferriepeter ferrie

4,5413 gold badges17 silver badges33 bronze badges

0

I have a small executable that I downloaded from the net, and that runs in the Command Line, which makes me think it may be a DOS program. The program works perfectly, but due to being developed by a non-English speaker, the interface/presentation of it needs to be cleaned up to make it look a little more professional. Is it possible to get to the file’s source code and edit it?

asked Jan 1, 2016 at 20:05

Hashim Aziz's user avatar

1

Principally: Yes.
But: it’s not practically.

You could change the machine-code within the .exe, but that’s not really practically if you want to change the interface/presentation. Also you would need to do it at least in assembly language.
If you want to improve the software, I think, the best would be to contact the developer and ask if you can help him to improve the software.

If you want to take a look inside the .exe anyway you could try OllyDbg.

If you want to change something in such an existing program, you have basically 3 ways to do it:

  1. Changing the machine code itself:
    The representation of the machine code does not need to be 1’s and 0’s, you can use any other number system. Anyway machine code is really, really hard to read and edit. Additionally you have also the same problems like you ‘simply’ disassemble the program.

  2. Use a disassembler and modify the assembler code:
    You have to take care of the memory layout of the software you want to modify. Since any change on some string could override another variable. And assembler is also not easy to read and write. So depending on the size of the software, it takes some time to get a basic overview about where to change the code.

  3. Or use a decompiler:
    But you mostly won’t get any useful variable names, since the compiler removes them most of the time.

Another point you have to take a look at is anti debugging and reverse engineering tools. They may prevent from running the piece of code inside debugger like OllyDbg or getting useful decompiled code.
Also take a look at peter ferrie’s answer.

Community's user avatar

answered Jan 1, 2016 at 20:28

falx's user avatar

falxfalx

664 bronze badges

4

The short answer is no — the source is not available if only the .exe is available. The source code is an entirely separate file which is generally not shared with the public. However, given the .exe file, it might be possible to «decompile» it into a form of source code which would allow a new .exe file to be produced, and which should match the existing one fairly well when performing a byte-for-byte comparison.

With that decompiled source code in hand, it would be possible to make modifications to the behavior or appearance of the program, but it would be far from trivial, since such relatively important things as variable names will not be present, so deriving the meaning of certain memory accesses will require a lot of time and effort.

You would need to consider carefully if the effort is worth the reward.

answered Jan 2, 2016 at 5:18

peter ferrie's user avatar

peter ferriepeter ferrie

4,5413 gold badges17 silver badges33 bronze badges

0

#статьи

  • 6 окт 2020

  • 12

Что такое рефакторинг кода и зачем он нужен

Программу можно написать тысячей разных способов, и она будет работать. Но код придётся почистить.

 vlada_maestro / shutterstock

Марина Демидова

Программист, консультант, специалист по документированию. Легко и доступно рассказывает о сложных вещах в программировании и дизайне.

Рефакторинг — это переработка исходного кода программы, чтобы он стал более простым и понятным.

Рефакторинг не меняет поведение программы, не исправляет ошибки и не добавляет новую функциональность. Он делает код более понятным и удобочитаемым.

Например, вот фрагмент на Python, создающий список из строки:

list = []
for char in 'abcdef':
 if char != 'c':
	list.append(char * 2)
print(list) # ['aa','bb','dd','ee','ff']

При рефакторинге его можно упростить, применив конструктор списков:

list = [char * 2 for char in 'abcdef' if char != 'i']
print (list)

Результат работы программы не изменился, но код стал проще, компактнее и понятнее.

Последовательность таких небольших изменений может сильно улучшить качество проекта.

Стройный, хорошо структурированный код легко читается и быстро дорабатывается. Но редко удаётся сразу сделать его таким. Разработчики спешат, в процессе могут меняться требования к задаче, тестировщики находят баги, которые нужно быстро исправить, или возникают срочные доработки, и их приходится делать второпях.

В результате даже изначально хорошо структурированный исходник становится беспорядочным и непонятным. Программисты знают, как легко завязнуть в этом хаосе. Причём неважно, чужой это код или собственный.

Чтобы решить все эти проблемы, делается рефакторинг программы. В новом проекте он нужен, чтобы:

  • сохранить архитектуру проекта, не допустить потери структурированности;
  • упростить будущую жизнь разработчиков, сделать код понятным и прозрачным для всех членов команды;
  • ускорить разработку и поиск ошибок.

Но любое приложение со временем устаревает: язык программирования совершенствуется, появляются новые функции, библиотеки, операторы, делающие код проще и понятнее. То, что год назад требовало пятидесяти строк, сегодня может решаться всего одной.

Поэтому даже идеальная когда-то программа со временем требует нового рефакторинга, обновляющего устаревшие участки кода.

Программный код предназначен не только для компьютера, но и для человека, который будет его дорабатывать. Плохо, если ему придётся неделю разбираться в исходниках, чтобы изменить в программе несколько строк. И не исключено, что этим человеком окажетесь вы сами.

Рефакторинг — не оптимизация, хотя и может быть с нею связан. Часто его проводят одновременно с оптимизацией, поэтому понятия кажутся синонимами. Но у этих процессов разные цели.

Цель оптимизации — улучшение производительности программы, а рефакторинга — улучшение понятности кода. После оптимизации исходный код может стать сложнее для понимания.

После рефакторинга программа может начать работать быстрее, но главное — её код становится проще и понятнее.

Признаки, показывающие, что назрела необходимость в рефакторинге:

  • Программа работает, но даже небольшие доработки сильно затягиваются из-за того, что каждый раз приходится долго разбираться в коде.
  • Разработчик постоянно не может точно сказать, сколько времени ему нужно на выполнение задачи, потому что “там надо вначале разбираться”.
  • Одинаковые изменения приходится вносить в разные места текста программы.

Такой код нужно срочно рефакторить, иначе он будет тормозить реализацию проекта и затруднять внесение правок.

Вообще рефакторинг нужно проводить постоянно. Делайте его каждый раз, после того как поменяли программу и убедились, что всё работает. Например, если добавили или изменили какую-то функцию, метод, класс или объявили новую переменную.

Рефакторинг — это маленькие последовательные улучшения кода. Чистить можно всё, но в первую очередь найдите эти проблемы:

  1. Мёртвый код. Переменная, параметр, метод или класс больше не используются: требования к программе изменились, но код не почистили. Мёртвый код может встретиться и в сложной условной конструкции, где какая-то ветка никогда не исполняется из-за ошибки или изменения требований. Такие элементы или участки текста нужно удалить.
  2. Дублирование. Один и тот же код выполняет одно и то же действие в нескольких местах программы. Вынесите эту часть в отдельную функцию.
  3. Имена переменных, функций или классов не передают их назначение. Имена должны сообщать, почему элемент кода существует, что он делает и как используется. Если видите, что намерения программиста непонятны без комментария, — рефакторьте.

    Примеры корректных имен: totalScore — переменная, означающая итоговый счёт в игре, maxWeight — максимальный вес. Для функций и методов лучше использовать глаголы, например: saveScore () — сохранить счет, setSize () — задать размер, getSpeed () — получить скорость.

  4. Слишком длинные функции и методы. Оптимальный размер этих элементов — 2-3 десятка строк. Если получается больше, разделите функцию на несколько маленьких и добавьте одну общую. Пусть маленькие выполняют по одной операции, а общая функция их вызывает.
  5. Слишком длинные классы. То же самое. Оптимальная длина класса — 20–30 строк. Разбейте длинный класс на несколько маленьких и включите их объекты в один общий класс.
  6. Слишком длинный список параметров функции или метода. Они только запутывают, а не помогают. Если все эти параметры действительно нужны, вынесите их в отдельную структуру или класс с понятным именем, а в функцию передайте ссылку на него.
  7. Много комментариев. Плохой код часто прикрывается обильными комментариями. Если почувствовали желание пояснить какой-то участок кода, попробуйте сначала его переписать, чтобы и так стал понятным. Бесполезные комментарии загромождают программу, а устаревшие и неактуальные вводят в заблуждение.

После каждой правки посмотрите на соседние участки кода: возможно, их тоже стоит поправить и сделать понятнее. И на те участки кода, которые давно не редактировались, — они уже могли стать некорректными.

После каждого изменения программу надо тестировать, поэтому перед началом рефакторинга подготовьте комплект тестов: модульных, функциональных или интеграционных. Изменения при рефакторинге вносятся небольшие, так что ошибки обычно легко найти и исправить.

Код чистят и на этапе тестирования, когда всё уже готово и проверяется работоспособность программы. Тут разработчик выполняет требования тестировщиков и одновременно проводит рефакторинг.

Как правило, руководители проектов понимают важность рефакторинга и делают его элементом разработки. Особое место он занимает в экстремальном программировании, когда программисты попеременно то пишут код и разрабатывают тесты, то проводят рефакторинг написанного.

Не страдайте перфекционизмом! Если вы поправили какой-то кусочек кода, не надо перетряхивать всю программу, разыскивая, что ещё можно улучшить. Стремление к совершенству вечно, но лучше обойтись без фанатизма.

Мы всё-таки меняем рабочий код. Тут можно не только всё упростить, но и сильно напортачить. Небрежный рефакторинг может отбросить выполнение проекта на дни и недели.

Опасно делать рефакторинг не постоянно, а от случая к случаю. Соблазн сильно улучшить код становится невыносимым. Вы всё глубже закапываетесь в программу и копаете себе яму, в которой легко увязнуть.

Рефакторьте постоянно и по чуть-чуть.

Иногда бывают злоупотребления: рефакторинг может стать способом саботажа, отговоркой, с помощью которой откладываются важные релизы и внедрение новых фич.

Но всё равно нельзя пренебрегать усовершенствованием кода, потому что это лучший способ ускорить работу в будущем.

Хотите создавать эффективные приложения с минимальным количеством ошибок, чистым и понятным кодом? На наших курсах учат не только языкам программирования, но и процессу разработки: проектированию, рефакторингу, тестированию и дебагу.

Учись бесплатно:
вебинары по программированию, маркетингу и дизайну.

Участвовать

Школа дронов для всех
Учим программировать беспилотники и управлять ими.

Узнать больше

Понравилась статья? Поделить с друзьями:
  • Как изменить исходный код приложения на андроид
  • Как изменить использование выделенной памяти графического процессора
  • Как изменить использование видеокарты на ноутбуке
  • Как изменить исполняемый файл агент сервера 1с
  • Как изменить исполняемый файл exe