ローリングコンバットピッチなう!

AIとか仮想化とかペーパークラフトとか

ChatGPTにリバースエンジニアリング(プログラムの要約)をさせてみた

ChatGPTにソースコードからプログラムの概要説明を作ってもらう実験をやってみました。
世の中にはドキュメントが無く、なんならコメントもほぼ入っていないソースコードがたくさんあってツール等で手軽に仕様書作りたい需要はたくさんあると思います。

手持ちの実験用ソースコード(大半は当ブログで紹介してことがあるもの)を何本かChatGPTさんに
「Please explain what the following source code does briefly.」
とお願いしてみました。英語でお願いしているのは現時点でChatGPTは英語の方が日本語よりはるかに高速かつ確実に処理されるため。

1本目

rc30-popo.hatenablog.com


1)ソースコード

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <sys/timerfd.h>
#include <time.h>

volatile sig_atomic_t stop_flag = 0;
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

void abort_handler(int sig);
void thread_func(union sigval arg);
void thread_func2(void);

void abort_handler(int sig){
	stop_flag = 1;
}


void thread_func(union sigval arg){
	printf("Tid = %lu\n",pthread_self());
	pthread_cond_signal(&cond);
}
void thread_func2(void){
	int ret;
	struct timespec curTime,lastTime;
	clock_gettime(CLOCK_REALTIME,&lastTime);

	pthread_mutex_lock(&m);
	while(1){
		ret = pthread_cond_wait(&cond,&m);
		if(ret == 0){
			clock_gettime(CLOCK_REALTIME,&curTime);
			if(curTime.tv_nsec < lastTime.tv_nsec){
				printf("Interval = %10ld.%09ld\n",curTime.tv_sec - lastTime.tv_sec - 1,curTime.tv_nsec + 1000000000 - lastTime.tv_nsec);
			}
			else{
				printf("Interval = %10ld.%09ld\n",curTime.tv_sec - lastTime.tv_sec,curTime.tv_nsec - lastTime.tv_nsec);
			}
			lastTime = curTime;
		}
		if(stop_flag){
			break;
		}

	}
	pthread_mutex_unlock(&m);

}


int main(int argc,char *argv[]){
	timer_t timer_id;
	struct itimerspec ts;
	struct sigevent se;
	int status;
	pthread_t thread;
	int ret;

	if (signal(SIGINT,abort_handler) == SIG_ERR){
		printf("Singal Handler set error!!\n");
		exit(1);
	}
	se.sigev_notify = SIGEV_THREAD;
	se.sigev_value.sival_ptr = &timer_id;
	se.sigev_notify_function = thread_func;
	se.sigev_notify_attributes = NULL;

	ts.it_value.tv_sec = 2;
	ts.it_value.tv_nsec = 0;
	ts.it_interval.tv_sec = 2;
	ts.it_interval.tv_nsec = 0;
	
	status = timer_create(CLOCK_MONOTONIC,&se,&timer_id);
	if(status == -1){
		printf("Fail to creat timer\n");
		exit(1);
	}
	status = timer_settime(timer_id,0,&ts,0);
	if(status == -1){
		printf("Fail to set timer\n");
		exit(1);
	}
        ret = pthread_create(&thread,NULL,(void *)thread_func2,NULL);
	if(ret != 0){
		printf("Cannot create thread!!\n");
		exit(1);
	}


	ret = pthread_join(thread,NULL);
	if(ret != 0){
		printf("Cannot join thread!!\n");
		exit(1);
	}

	timer_delete(timer_id);

	return 0;
}

2)ChatGPT回答

割とキレイに纏めてくれています。このソース、以前の当ブログのエントリーでは1秒間隔のタイマーにしていたのですが、このテストでは2秒間隔に変更しています。なので、GhatGPTが以前のエントリー内容をただ学習してコピペしたわけではなさそうに見えます。

This source code sets up a timer that fires every 2 seconds, and a thread that waits for the timer to fire and prints the time interval between each timer event. The program also catches the SIGINT signal (generated by pressing Ctrl-C) and sets a flag to stop the timer and terminate the program. Here is a brief summary of what each function does:

abort_handler: This function is called when the program receives the SIGINT signal. It sets the stop_flag variable to 1, which signals the timer thread to stop waiting and terminate the program.
thread_func: This function is called by the timer thread every time the timer fires. It prints the thread ID and signals the main thread to wake up and check the time interval.
thread_func2: This function is the main function of the timer thread. It waits for the signal from thread_func using a condition variable and mutex, and calculates and prints the time interval between each timer event.
main: This function sets up the timer and timer thread, and waits for the thread to terminate before cleaning up the timer. It also installs the abort_handler function to catch the SIGINT signal.

上記ソースのtv_nsecを設定している2箇所を

	ts.it_value.tv_sec = 2;
	ts.it_value.tv_nsec = 500000000;
	ts.it_interval.tv_sec = 2;
	ts.it_interval.tv_nsec = 500000000;

こんな感じで書き換えて再度試すと以下の様な概要説明を作ってくれました。
ちゃんとタイマーインターバルを計算していますね。
(この後に細々とコードの流れの説明を作ってくれたのですが、それは割愛)

This is a C program that creates a timer using the POSIX timer API and a thread to handle the timer events. The timer is set to expire every 2.5 seconds, and when it expires, the thread wakes up and prints the amount of time that has elapsed since the previous expiration. The program runs until it receives an interrupt signal (SIGINT), at which point it cleans up and exits.

2本目

これは当ブログでは取り上げたことは無いはず。
OpenCVを利用して複数のビデオファイルを水平に結合して1本のビデオファイルにするサンプルソースです。
コメントには「2本のビデオファイルを結合」と入っていますが、実際は3本結合しています。
コメントには騙されず、ソースコードの内容から説明文を作っている様に見えます。

1)ソースコード

# -*- Coding: utf-8 -*-
# OpenCV test
#   Concat two video images

import cv2

input_video_file0 = './in0.mp4'
input_video_file1 = './in1.MOV'
input_video_file2 = './in2.mp4'
output_video_file = './out.mp4'

input_video0 = cv2.VideoCapture(input_video_file0)
input_video1 = cv2.VideoCapture(input_video_file1)
input_video2 = cv2.VideoCapture(input_video_file2)

TARGET_WIDTH = 320
TARGET_HEIGHT = 240

open_check_error = False
if input_video0.isOpened() == False:
    print('Error: Fail to open ' + input_video_file0)
    open_check_error = True
if input_video1.isOpened() == False:
    print('Error: Fail to open ' + input_video_file1)
    open_check_error = True
if input_video2.isOpened() == False:
    print('Error: Fail to open ' + input_video_file2)
    open_check_error = True

if open_check_error:
    input_video0.release()
    input_video1.release()
    input_video2.release()
    exit(1)


video_fps = input_video0.get(cv2.CAP_PROP_FPS)

video_frame_num = min(input_video0.get(cv2.CAP_PROP_FRAME_COUNT),input_video1.get(cv2.CAP_PROP_FRAME_COUNT),input_video2.get(cv2.CAP_PROP_FRAME_COUNT))

print('frame_num = ',video_frame_num)
print('fps = ',video_fps)
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')

output_video = cv2.VideoWriter(output_video_file, int(fourcc), video_fps, (TARGET_WIDTH * 3, TARGET_HEIGHT))
target_size = (TARGET_WIDTH,TARGET_HEIGHT)
print('resize target = ',target_size)

for i in range(int(video_frame_num)):
    print('\rProcessing Frame No %10d/%10d' % (i+1,int(video_frame_num)),end='')
    ret, frame0 = input_video0.read()
    ret, frame1 = input_video1.read()
    ret, frame2 = input_video2.read()
    frame0_mini = cv2.resize(frame0,target_size,interpolation=cv2.INTER_AREA)
    frame1_mini = cv2.resize(frame1,target_size,interpolation=cv2.INTER_AREA)
    frame2_mini = cv2.resize(frame2,target_size,interpolation=cv2.INTER_AREA)
    concat_frame = cv2.hconcat([frame0_mini,frame1_mini,frame2_mini])
    output_video.write(concat_frame)

print('\nvideo output complete')

input_video0.release()
input_video1.release()
input_video2.release()
output_video.release()
exit(0)

2)ChatGPT回答

The Python code is using OpenCV library to combine three input video files (in0.mp4, in1.MOV, in2.mp4) side-by-side into a single output video file (out.mp4). The input videos are read frame by frame, resized to a target size of 320x240 pixels, concatenated horizontally, and written to the output file using the VideoWriter class. The output video has the same frame rate as the first input video and is saved in mp4 format. If any of the input videos fails to open, the program will exit with an error code of 1. Once the processing is complete, all video objects are released, and the program exits with a success code of 0.

3本目

これはコメントから何やっているかは割と推定できるケースです。


rc30-popo.hatenablog.com
rc30-popo.hatenablog.com

1)ソースコード

# -*- Coding: utf-8 -*-

# numpy
import numpy as np

# Matplotlib
import matplotlib as mpl
mpl.use('TkAgg')  # or whatever other backend that you want
import matplotlib.pyplot as plt

# シミュレーション日数
SIM_DAYS = 180
# 感染後、陰性になるまでの平均日数
AVG_RECOVERY_DAYS = 10
# 重症となる割合
SEVERE_PATIENT_RATE = 0.20
# 重症化するまでの平均日数
AVG_SEVERE_DAYS = 7
# 一人の陽性者が1日あたりに感染させる平均人数
AVG_INFECTION_RATE_PER_PERSON = 0.2
# 世界人口
#WORLD_POPULATION = 7700000000
WORLD_POPULATION = 100000

# シミュレーション停止レシオ(対世界人口)
SIM_STOP_RATIO=0.9

# 移動制限等の対策パラメータ
# 対策有無
CONTROL_MEASURE=False
# 対策を開始する日
CONTROL_START_DAY=60
# 対策による感染率低減
CONTROL_FACTOR = 0.4

# グラフ上の現在日目安
CURRENT_DAY=80

def controlFactor(day):
    global CONTROL_MEASURE
    global CONTROL_START_DAY
    global CONTROL_FACTOR
    retvalue = 1.0
    if CONTROL_MEASURE:
        if day >= CONTROL_START_DAY:
            retvalue = CONTROL_FACTOR
    return retvalue

# 日別の新規感染者
new_infected = np.zeros(SIM_DAYS+1)
new_infected[0] = 1
# 日別の累積感染者
total_infected = np.zeros(SIM_DAYS+1)
total_infected[0] = 1
# 重症化して陽性判定される患者数
new_detected = np.zeros(SIM_DAYS+1)
# 重症化して陽性判定される患者数の累積
total_detected = np.zeros(SIM_DAYS+1)



sim_stop_day = SIM_DAYS
# シミュレーション
for day in range(1,SIM_DAYS + 1):
    # その日新規に感染する人数
    day_idx = (day - AVG_RECOVERY_DAYS) if day - AVG_RECOVERY_DAYS >= 0 else 0
    ctrl_factor = controlFactor(day)
    infected_cnt = np.sum(new_infected[day_idx:day] * AVG_INFECTION_RATE_PER_PERSON * ctrl_factor * (WORLD_POPULATION - total_infected[day - 1])/WORLD_POPULATION)
    new_infected[day] = infected_cnt
    total_infected[day] = total_infected[day - 1] + infected_cnt

    day_idx2 = day -  AVG_SEVERE_DAYS
    if day_idx2 >= 0:
        new_detected[day] = new_infected[day_idx2] * SEVERE_PATIENT_RATE
    total_detected[day] = total_detected[day - 1] + new_detected[day]

    print('day = %d, new_infected = %f, new_detected = %f,total_infected = %f,total_detected = %f' % (day,infected_cnt,new_detected[day],total_infected[day],total_detected[day] ))

    if total_infected[day] >= WORLD_POPULATION * SIM_STOP_RATIO: # 感染者が世界人口x停止レシオ超えたら停止
        print('Total infected count exceeds World population. x %f' % SIM_STOP_RATIO)
        sim_stop_day = day
        break



# グラフ描画
days = np.arange(sim_stop_day + 1)
fig = plt.figure()
ax1 = fig.add_subplot(221)
ax2 = fig.add_subplot(222)
ax3 = fig.add_subplot(223)
ax4 = fig.add_subplot(224)

ax1.plot(days[0:sim_stop_day + 1],total_infected[0:sim_stop_day + 1],label='Total Infected')
#ax1.hlines([WORLD_POPULATION],0,sim_stop_day,"red",linestyles='dashed')
ax2.bar(days[0:sim_stop_day + 1],new_infected[0:sim_stop_day + 1],label='Newly Infected')
ax3.plot(days[0:sim_stop_day + 1],total_detected[0:sim_stop_day + 1],label='Total Detected')
#ax3.vlines(CURRENT_DAY,0,max(total_detected[0:sim_stop_day + 1]),"blue",linestyles='dashed')
ax4.bar(days[0:sim_stop_day + 1],new_detected[0:sim_stop_day + 1],label='Newly Detected')
#ax4.vlines(CURRENT_DAY,0,max(new_detected[0:sim_stop_day + 1]),"blue",linestyles='dashed')

ax1.legend()
ax2.legend()
ax3.legend()
ax4.legend()

plt.show()

2)ChatGPT回答
だいたい合っているのですが、最後のセンテンスにある「the impact of various control measures, such as reducing the infection rate or limiting mobility,」は正しく無いですね。そこまでやってないです。
おそらく同様のシミュレーションを行っている他のプログラムとの類似性からの推論でこの文章が組み立っているのでは無いでしょうか。
ソースコード書いた本人も一瞬騙されてそうになるんですが、ChatGPTの回答を鵜呑みに出来ない部分ですね。

よくよく考えたら「the impact of various control measures, such as reducing the infection rate or limiting mobility,」は「CONTROL_MEASURE,CONTROL_START_DAY,CONTROL_FACTOR」の3定数の効果を言っている様に見えます。
巧みに詳細説明を避けてなんとでも解釈できる表現をしている気はするけど、間違ってはいないなあ。

This is a Python source code that simulates the spread of a virus over time. The simulation includes parameters such as the number of days to simulate, the average number of days it takes for a patient to recover, the rate at which patients become severely ill, and the average number of days it takes for a patient to become severely ill.

The simulation uses numpy and matplotlib libraries for data manipulation and visualization, respectively. The simulation starts with one infected person and calculates the number of newly infected and detected patients each day. The simulation stops when the number of infected patients exceeds a predetermined percentage of the world's population.

The source code also includes parameters to simulate the impact of various control measures, such as reducing the infection rate or limiting mobility, and graphs to visualize the data.

4本目

https://rc30-popo.hatenablog.com/entry/2019/09/07/221712
1)ソースコード

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import signal
import sys
from urllib.parse import urlparse
import mysql.connector

PRIV_LOG = 'privoxy.log'
DB_HOST = 'localhost'
DB_USER = 'metabase'
DB_PW = 'metabasetest'
DB_NAME = 'metabasedb'



#def do_exit(sig, stack):
#    print('You pressed Ctrl+C.')
#    print('Stop running the job.')
#    sys.exit(0)

if __name__ == '__main__':
#    signal.signal(signal.SIGINT, do_exit)


    with open(PRIV_LOG, mode='r') as file:
        config = { 'user':DB_USER,'password':DB_PW,'host':DB_HOST,'database':DB_NAME }
        cnx = mysql.connector.connect(**config)
        cursor = cnx.cursor()

        seq = 0
        for line in file:
            args = line[:-1].split()
            if len(args) != 0:
                op = 'unknown'
                urlsrc = ''
                dt = args[0]
                tm = args[1][:-4]
                id = args[2]
                if args[3] == 'Request:':
                    op = 'request'
                    urlsrc = args[4]
                    o = urlparse('http://'+urlsrc)
                    url = o.netloc
                elif args[3] == 'Crunch:':
                    if args[4] == 'DNS':
                        op = 'dns_error'
                        urlsrc = args[6]
                        o = urlparse(urlsrc)
                        url = o.netloc
                    elif args[4] == 'Blocked:':
                        op = 'block'
                        urlsrc = args[5]
                        if urlsrc.startswith('http:'):
                            o = urlparse(urlsrc)
                            url = o.netloc
                        else:
                            url = urlsrc
                print(seq,dt,tm,op,url)
                if len(url) > 128:
                    url = url[0:128]
                stmt = "INSERT INTO privoxylog (seqno,time,operation,netloc) VALUES (null,%s,%s,%s)"
                cursor.execute(stmt,(dt+' '+tm,op,url))
                seq += 1
        cursor.execute('COMMIT')
        cursor.close()
        cnx.close()

2)ChatGPT回答

Privoxyオープンソースのプロキシーサーバーです。
広告フィルタリング等に用いるものでこのスクリプトはそのログをMySQLに書き込むためのものです。
という部分は正しく文章化されています。



www.privoxy.org


This is a Python script that reads a log file generated by Privoxy, a non-caching web proxy with advanced filtering capabilities, and inserts the log data into a MySQL database.

Here is a brief explanation of the script:

The script imports the necessary modules, including signal, sys, urlparse from urllib.parse, and mysql.connector.
Several constants are defined, including the name of the Privoxy log file, the host, user, password, and database name for the MySQL database.
The script opens the Privoxy log file in read mode, connects to the MySQL database, and creates a cursor to execute SQL statements.
The script then iterates through each line in the Privoxy log file, parses the log data, and inserts the data into the MySQL database.
The SQL INSERT statement is constructed using the parsed log data and executed using the cursor.
The script then closes the cursor, commits the transaction, and closes the database connection.
Note that the code for handling a Ctrl+C signal has been commented out and is not used in the script

このスクリプトの肝はPrivoxyのログのフィールド構成がログの種類に依存した固定長になっている部分があり、そこをスクリプトで切り分けています。
そのレベルの詳細はスルーされていますが、概要説明としては正しいです。
Privoxyを使っているということはコメント等では明示しておらず「privoxy.log」というログファイル名のみから推論されていると思われます。入力ファイル名を引数で渡す形にしてスクリプト上明示しなかった場合にどの様な説明を作ってくれるか興味深いのですが、そういったテストはいずれやってみたいと思います。
【2023/04/11追記】
よくよくChatGPTの要約を読むと「The script then closes the cursor, commits the transaction, 」となっていますね。これ順番逆ですね。「The script then commits the transaction, closes the cursor, 」(トランザクションをコミットが先、cursorのクローズが後)が正しい。
そういうとこだぞ!!っと...

所感

既にさんざん言われている事ですが、ChatGPTはファクトを積み上げて論理的に考証して回答を返すわけではないので、割とテキトーに書いているなと見える部分が色々あります。一方で対象プログラムを何をやっているのか?ソースコードしか無い状態で7〜8割推定レベルで合っていれば取り敢えず良いという状況下、かつChatGPTの生成した回答を検証する能力が使う側にあればかなり効率的に文書化ができそうに見えます。

他にも試したのですが、だいたい概要はOK、細かい処理条件はいい感じにすっとぼけて無視します。
それはそれで強いなあ..人間でもそういうスキル持った人いますよねえ。