최근에 COBOL로 된 코드를 잔뜩 볼 일이 생겼습니다. 코볼 소스 라인이 긴게 주석을 합쳐서 2만 라인이 넘는 것들도 있고해서 분석할 엄두가 안 나더군요.. 어떻게 call flow라도 알 방법이 없을까 하고 소프트웨어를 뒤져보니 상용만 있고(과자 구하기가 힘들더군요) 오픈소스쪽도 소스포지를 좀 뒤져봤는데(ㅠ.ㅠ 나중에 얼마 안지나서 이상한 이름으로 존재하는 것이 있더군요..) 이 답답한 사정을 shining군에게 토로했더니, 30분 정도 브레인스토밍 30분 짝 프로그래밍을 해서 약식의 콜 흐름을 그려봤습니다.
일단 해야할 일을 정의했습니다. call flow를 그린다는 일은 함수가 함수를 부르는 것을 그래프로 보여주는 작업입니다. 즉 caller와 callee를 모두 구하면 됩니다. 그래프를 그려주는건 리눅스에서 library dependencies를 간단하게 graphviz로 그려주는 쉘스크립트를 보고 참조하기로 했고, 코볼의 문법 자체가 워낙에 영문에 가까워서 파싱 자체가 어렵지 않았습니다.
함수를 나타내는 부분
- 함수명 SECTION.
- 데이터섹션 예약어는 제외합니다.
함수를 부르는 부분
- CALL "외부함수"
- CALL '외부함수'
- PERFORM 내부함수
- PERFORM으로 시작하는 것중 문법적인 예외사항 제거
일단 가능한 빠른 시간내에 구현을 제약사항으로 처리했기 때문에 최대한 간단하게 호출하는 3부분만 파싱해서 구현했습니다. 중복제거 및 awk , shell 의존성 제거 먼저 저 3 가지 타입만 가볍게 긁어오는 것을 shining군이 awk로 작성을 했습니다. 정규표현식으로 해당 부분을 긁어서 graphviz용 입력파일을 작성했습니다. 생성된 그래프 파일입니다. (함수 이름은 모두 변경했습니다.)
 코드는 다음과 같습니다.
#!/usr/bin/env ruby
# FILE EXTENSION $COBOL_EXTENSION = '.cbl' $DOT_EXTENSION = '.dot' $PNG_ENTENSION = '.png'
# DOT SETTING if RUBY_PLATFORM.match(/mswin32$/) then # windows $DOT_EXECUTE=ENV['ProgramFiles'] + '\Graphviz\bin\dot.exe' if ENV['ProgramFiles'] $FONTNAME="Tahoma" else $DOT_EXECUTE="dot" end $FONTSIZE=12
# perform reserve words PERFORM_RESERVES = [ 'AFTER', 'BEFORE', 'BY', 'FROM', 'TEST', 'UNTIL', 'VARYING', 'WITH' ]
def cobol_call_flow_generate( filename ) # cobol file extension check return if File.extname(filename) != $COBOL_EXTENSION
# dot tempoary filename dot_filename = ARGV[0]+$DOT_EXTENSION # dot output image filename output_filename = ARGV[0]+$PNG_ENTENSION # function call related calls = []
# current sections name current_function = "INVALID-SECTION" # cobol file read & parsing File.open(ARGV[0], 'r') { |f| while input_line = f.gets case input_line when /^\s*\*/ next when /^\s*([A-Z0-9-]*)\s*SECTION\.\s*\n$/ current_function = $1 when /^\s*PERFORM\s*([A-Z0-9-]*)/ calls << [ current_function, $1, :perfom ] unless PERFORM_RESERVES.include?($1) or calls.include?([ current_function, $1, :perform ]) when /^\s*CALL\s*['"](.*)['"]/ calls << [current_function, $1, :call ] unless calls.include?([current_function, $1, :call ]) end end }
# write dot file File.open(dot_filename, 'w') { |wf| wf.puts('digraph DependencyTree {') wf.puts(" node [ fontname=\"#$FONTNAME\", fontsize=#$FONTSIZE ]; "); wf.puts(' "A-MAIN" [shape=Mdiamond];') wf.puts(' "Z-FINISH" [shape=Mdiamond];') calls.each { |i| wf.puts(" \"#{i[1]}\" [shape=diamond,style=filled,color=lightgray]; ") if i[2] == :call wf.puts(" \"#{i[0]}\" -> \"#{i[1]}\"") } wf.puts('}') } # execute dot dot_command = "\"#{$DOT_EXECUTE}\" -Tpng #{dot_filename} -o #{output_filename}" system(dot_command) # remove tempoary dot file require 'fileutils' FileUtils.rm(dot_filename) end
# main if ( ARGV.size < 1 ) puts "#{File.basename $0} [cobol files]" exit 0 end
ARGV.each { |cobol_file| cobol_call_flow_generate( cobol_file ) }
현재 가지고 있는 문제점은 다음과 같습니다.
하나의 함수에서 여러번 콜을 하는 것을 고려하지 않았습니다.
- 외부함수와 내부함수를 구분하지 않습니다.
- 다른 파일을 찾아서 연동하는 부분이 빠져있습니다.
- C 함수와 COBOL함수를 구분하지 않습니다.
|